Análisis de Datos de Imágenes FNA para el Diagnóstico de Cáncer de Mama

Autores: Claudia Heredia Ceballos, Manuel Otero Barbasán, Marta Pineda Gisbert, Javier Fernández Castillo.

Grupo: G5.

Organización: Universidad de sevilla.

Resumen

Este proyecto tiene como objetivo la aplicación de técnicas de aprendizaje supervisado y no supervisado para el diagnóstico de cáncer de mama, utilizando un conjunto de datos que contiene características extraídas de imágenes de aspirado con aguja fina (FNA) de masas mamarias. El conjunto de datos contiene 569 muestras, con 357 benignas y 212 malignas, y 33 características que describen diversas propiedades de los núcleos celulares en las imágenes. En este estudio, se explorarán varios enfoques de clasificación supervisada, como CART, Random Forest, SVM, Regresión logística y redes neuronales para predecir el diagnóstico de las muestras, además de aplicar técnicas no supervisadas como el clustering y la reducción de dimensionalidad a través de PCA y T-SNE. Se espera que el análisis combinado de ambos enfoques permita mejorar la precisión en el diagnóstico y proporcionar una mejor comprensión de los patrones subyacentes en los datos. Se discutirán los resultados obtenidos y se presentarán conclusiones sobre la efectividad de las técnicas empleadas.

1. Introducción

El diagnóstico temprano y preciso del cáncer de mama es crucial para un tratamiento exitoso. En este estudio, se emplea el conjunto de datos Breast Cancer Wisconsin (Diagnostic) Data Set, que contiene características derivadas de imágenes FNA de células de tejido mamario.

El FNA es una técnica de diagnóstico utilizada para evaluar la naturaleza de las masas mamarias. Durante el procedimiento, se extrae una pequeña cantidad de tejido de la masa y se examina bajo un microscopio para determinar si es benigna o maligna. Las características de las células, como la forma, el tamaño y la textura de los núcleos, pueden proporcionar información valiosa sobre la naturaleza del tumor.

En la Figura 1 se ilustra el procedimiento de aspiración con aguja fina (FNA), que muestra la inserción de la aguja para extraer las células del tumor. La Figura 2 presenta una imagen microscópica de las células extraídas, permitiendo observar sus características una vez realizado el FNA.

Procedimiento de Aspiración con Aguja Fina (FNA)
Figura 1:Procedimiento FNA

Células extraídas de FNA
Figura 2: Células extraídas de FNA

El conjunto de datos incluye 33 características, como el radio, la textura, el perímetro y la simetría de los núcleos celulares, las cuales se utilizan para determinar si una muestra es benigna o maligna.

Estas características se pueden agrupar en:

Relacionadas con el tamaño
Incluyen medidas que describen el tamaño y la extensión de los núcleos celulares. El radio mide el tamaño de la circunferencia y se reporta como la media (radius_mean), el peor caso (radius_worst) y la desviación estándar (radius_se). El perímetro indica la longitud alrededor del núcleo, reportado de manera similar como media (perimeter_mean), peor caso (perimeter_worst) y desviación estándar (perimeter_se). Por último, el área representa el espacio ocupado por el núcleo, también dividido en media (area_mean), peor caso (area_worst) y desviación estándar (area_se).

Relacionadas con la forma
Estas variables describen características geométricas de los núcleos, enfocándose en irregularidades en su borde. La suavidad mide la regularidad de los bordes (smoothness_mean, smoothness_worst, smoothness_se), mientras que la compacidad evalúa qué tan redonda es la forma del núcleo (compactness_mean, compactness_worst, compactness_se). La concavidad mide el grado de hundimiento en los bordes (concavity_mean, concavity_worst, concavity_se) y los puntos cóncavos representan el número de puntos específicos donde estos hundimientos son evidentes (concave points_mean, concave points_worst, concave points_se).

Relacionadas con la textura y fractales
Estas características están asociadas a la apariencia y complejidad de la imagen de los núcleos celulares. La textura mide la variación en la intensidad de los píxeles y se reporta como media (texture_mean), peor caso (texture_worst) y desviación estándar (texture_se). Por su parte, la dimensión fractal describe la irregularidad del borde de los núcleos celulares y también se mide como media (fractal_dimension_mean), peor caso (fractal_dimension_worst) y desviación estándar (fractal_dimension_se).

El análisis se centra en la aplicación de técnicas de aprendizaje supervisado, como CART, Random Forest y SVM, para predecir el diagnóstico basado en las características numéricas. Además, se aplicarán técnicas de aprendizaje no supervisado, como el clustering y la reducción de dimensionalidad mediante PCA , para explorar los patrones ocultos en los datos y posiblemente mejorar la precisión de los modelos.

2. Metodología

El desarrollo de este proyecto se llevará a cabo a través de un enfoque estructurado y secuencial en varias fases, con el objetivo de analizar y predecir el diagnóstico de cáncer de mama utilizando imágenes FNA. Los pasos clave del proceso son los siguientes:

  1. Análisis Inicial y Preprocesamiento de los Datos
    En esta fase inicial, se procederá con la carga de los datos del conjunto de datos “Breast Cancer Wisconsin (Diagnostic)”, que contiene las características extraídas de imágenes de aspiración con aguja fina (FNA). Se realizará una exploración preliminar de los datos para comprender la naturaleza de las variables y la distribución de las clases (benigno y maligno). Además, se identificará y gestionará cualquier valor faltante, y se evaluará la necesidad de realizar transformaciones adicionales, como la normalización o la conversión de variables categóricas.

  2. Aplicación de Modelos Supervisados
    En esta etapa, se implementarán y entrenarán varios modelos supervisados para la clasificación de los tumores como benignos o malignos. Los modelos seleccionados incluyen:

    • CART (Classification and Regression Trees): Un modelo de árbol de decisiones que permite realizar predicciones mediante una serie de reglas basadas en los datos de entrada.
    • Random Forest: Un conjunto de árboles de decisión que mejora la precisión mediante el uso de técnicas de muestreo y combinación de varios modelos.
    • SVM (Support Vector Machine): Un modelo que encuentra el hiperplano óptimo que separa las clases de manera eficaz.
    • Regresión logística: Un modelo estadístico que puede adaptarse a diferentes distribuciones de las variables dependientes, como la binomial en este caso.
      Se evaluará la Importancia de las Variables en los modelos para identificar las características más influyentes en las predicciones.
    • Redes Neuronales: Un modelo de aprendizaje profundo que puede capturar relaciones complejas entre las variables y adaptarse a patrones no lineales en los datos.
  3. Experimentación con el Dataset Reducido
    Posteriormente, se realizará una experimentación en la que se modificarán las características del dataset mediante la eliminación de variables menos relevantes, basándose en la importancia de las variables obtenida de los modelos. Esto permitirá optimizar los modelos y reducir el sobreajuste, mejorando la capacidad de generalización del sistema de diagnóstico.


  1. Aplicación de Modelos No Supervisados
    Para explorar los patrones subyacentes en los datos sin utilizar etiquetas de clase, se implementarán modelos no supervisados. Estos incluirán:
    • Clustering: En primer lugar se buscará el número de clusters con el método del codo y silhouette Se utilizarán técnicas como K-means,Clustering jerárquico o DBSCAN para agrupar las observaciones de acuerdo con similitudes en sus características. Esto permitirá identificar posibles grupos de datos que podrían no haber sido evidentes en la clasificación supervisada.
    • Reducción de Dimensionalidad: Para reducir el número de variables y simplificar el análisis, se emplearán técnicas como PCA (Principal Component Analysis) y T-SNE. Estas técnicas ayudarán a descubrir las estructuras latentes en los datos y facilitarán la visualización y el análisis posterior.
    • Algoritmo a priori: Se aplicará el algoritmo a priori para identificar reglas de asociación entre las características del dataset. Esto permitirá descubrir patrones interesantes y relaciones ocultas entre las variables.
  2. Presentación de los Resultados y Conclusiones
    Finalmente, se presentarán los resultados obtenidos de los modelos evaluados, incluyendo tablas, gráficos y análisis comparativos. En esta sección se discutirá la efectividad de las técnicas empleadas, destacando los modelos más precisos y los enfoques que mejor se ajustan a las necesidades del diagnóstico de cáncer de mama. Las conclusiones se centrarán en la viabilidad y los posibles pasos a seguir para mejorar el rendimiento del modelo y su aplicabilidad en un entorno clínico.

3. Implementación

En esta sección se muestran los pasos que se han seguido para la realización del proyecto. Incluye el Análisis Inicial y Preprocesamiento de los Datos, los modelos supervisados y no supervisados utilizados.

3.1. Análisis Inicial y Preprocesamiento de los Datos.

3.1.1. Importación de librerías

Se instalan los paquetes que serán necesarios durante el proyecto:

  • Tidyverse: Para la manipulación de datos y gráficos.
  • Caret: Para el preprocesamiento y modelado Lattice es requerido por Caret
  • DataExplorer: Para la exploración automatizada de los datos.
  • Dplyr: Proporciona una gramática de manipulación de datos.
  • Ggplot2: Personalización de gráficas.
  • Psych: Para análisis estadístico
  • Corrplot: Visualización de matriz de correlación.
  • Car: Para complementar técnicas de regresión.
  • pRoc: Proporciona herramientas para visualizar., suavizar y comparar curvas Roc.
# Nota: Descomentar las líneas de instalación si no se tienen los paquetes instalados. Comando ctrl+shift+c para descomentar.

# install.packages("tidyverse")
# install.packages("caret")
# install.packages("DataExplorer")
# install.packages("dplyr")
# install.packages("ggplot2")
# install.packages("lattice")
# install.packages("psych")
# install.packages("corrplot")
# install.packages("ggcorrplot")
# install.packages("car")
# install.packages("pROC")
# install.packages("flextable")
# install.packages("kernlab")
# install.packages("cluster")
# install.packages("dbscan")
# install.packages("Rtsne")
# install.packages("arules")
# install.packages("arulesViz")
library(tidyverse)
library(caret)
library(DataExplorer)
library(dplyr)
library(ggplot2)
library(lattice)
library(psych)
library(corrplot)
library(ggcorrplot)
library(pROC)
library(car)
library(flextable)
library(kernlab)
library(cluster)
library(dbscan)
library(Rtsne)
library(arules)
library(arulesViz)

3.1.2. Carga y visualización de datos

Para comenzar el análisis es necesario realizar la carga de los datos, se visualizan las primeras filas y la estructura de nuestro dataset.

data <- read.csv("data/data.csv")

head(data)
dim <- dim(data)

cat("Número de columnas:", dim[2], "\n")
Número de columnas: 33 
cat("Número de filas:", dim[1], "\n")
Número de filas: 569 

Como se puede observar, el dataset cuenta con 569 filas y 33 columnas.

A continuación, se muestra la estructura, tipos y algunos ejemplos de las variables del dataset para tener una visión global del las distintas variables con las que se cuenta.

str(data)
'data.frame':   569 obs. of  33 variables:
 $ id                     : int  842302 842517 84300903 84348301 84358402 843786 844359 84458202 844981 84501001 ...
 $ diagnosis              : chr  "M" "M" "M" "M" ...
 $ radius_mean            : num  18 20.6 19.7 11.4 20.3 ...
 $ texture_mean           : num  10.4 17.8 21.2 20.4 14.3 ...
 $ perimeter_mean         : num  122.8 132.9 130 77.6 135.1 ...
 $ area_mean              : num  1001 1326 1203 386 1297 ...
 $ smoothness_mean        : num  0.1184 0.0847 0.1096 0.1425 0.1003 ...
 $ compactness_mean       : num  0.2776 0.0786 0.1599 0.2839 0.1328 ...
 $ concavity_mean         : num  0.3001 0.0869 0.1974 0.2414 0.198 ...
 $ concave.points_mean    : num  0.1471 0.0702 0.1279 0.1052 0.1043 ...
 $ symmetry_mean          : num  0.242 0.181 0.207 0.26 0.181 ...
 $ fractal_dimension_mean : num  0.0787 0.0567 0.06 0.0974 0.0588 ...
 $ radius_se              : num  1.095 0.543 0.746 0.496 0.757 ...
 $ texture_se             : num  0.905 0.734 0.787 1.156 0.781 ...
 $ perimeter_se           : num  8.59 3.4 4.58 3.44 5.44 ...
 $ area_se                : num  153.4 74.1 94 27.2 94.4 ...
 $ smoothness_se          : num  0.0064 0.00522 0.00615 0.00911 0.01149 ...
 $ compactness_se         : num  0.049 0.0131 0.0401 0.0746 0.0246 ...
 $ concavity_se           : num  0.0537 0.0186 0.0383 0.0566 0.0569 ...
 $ concave.points_se      : num  0.0159 0.0134 0.0206 0.0187 0.0188 ...
 $ symmetry_se            : num  0.03 0.0139 0.0225 0.0596 0.0176 ...
 $ fractal_dimension_se   : num  0.00619 0.00353 0.00457 0.00921 0.00511 ...
 $ radius_worst           : num  25.4 25 23.6 14.9 22.5 ...
 $ texture_worst          : num  17.3 23.4 25.5 26.5 16.7 ...
 $ perimeter_worst        : num  184.6 158.8 152.5 98.9 152.2 ...
 $ area_worst             : num  2019 1956 1709 568 1575 ...
 $ smoothness_worst       : num  0.162 0.124 0.144 0.21 0.137 ...
 $ compactness_worst      : num  0.666 0.187 0.424 0.866 0.205 ...
 $ concavity_worst        : num  0.712 0.242 0.45 0.687 0.4 ...
 $ concave.points_worst   : num  0.265 0.186 0.243 0.258 0.163 ...
 $ symmetry_worst         : num  0.46 0.275 0.361 0.664 0.236 ...
 $ fractal_dimension_worst: num  0.1189 0.089 0.0876 0.173 0.0768 ...
 $ X                      : logi  NA NA NA NA NA NA ...
# sapply aplica una función a cada columna del dataframe
clases <- sapply(data, class)
clases_df <- data.frame(Clase = clases)

print(clases_df, row.names = FALSE)

Se obtienen las clases de cada columna en el conjunto de datos para entender mejor su tipo de datos y estructura. Esto permite visualizar rápidamente el tipo de variable (numérica, categórica, etc.) que se encuentra en el dataset.

Se visualiza un resumen estádistico de los datos.

describe(data)
Aviso: ningún argumento finito para min; retornando InfAviso: ningun argumento finito para max; retornando -Inf

Se verifica si existen valores faltantes en los datos utilizando la función anyNA(). Esto permite identificar rápidamente si hay datos incompletos en el dataset que puedan requerir limpieza o manejo especial.

anyNA(data)
[1] TRUE

Se observa que existen valores faltantes en el dataset. Para visualizar de manera más clara la cantidad de valores faltantes por columna, se utiliza la función plot_missing(data).

plot_missing(data)

Como puede observarse, se cuenta con 569 valores faltantes en la última columna “X”. Más adelante se procederá a eliminar esta columna.

3.1.3. Análisis exploratorio de los datos

En este paso el objetivo será entender la distribución y relaciones de variables.

3.1.3.1. Visualización de distribuciones y correlaciones:

Se visualiza un resumen gráfico general para entender las distribuciones y correlaciones entre las variables en el conjunto de datos. Esto ayuda a identificar patrones, distribuciones de frecuencia y posibles relaciones entre las diferentes columnas del dataset.

plot_intro(data)

Como se puede observar, el dataset cuenta con un 3% de columnas discretas, en concreto la columna “diagnosis” que es la variable objetivo, y un 94% de columnas continuas. Además, se observa un 3% de columnas con valores faltantes, que corresponden a la columna “X”.

Se visualizan las variables categóricas que dice si el tumor es “Maligno” o “Benigno” para contar cuántas observaciones hay de cada tipo. Esto permite ver la distribución de la columna diagnosis y entender la proporción entre ellas en el dataset.

diagnosis_counts <- table(data$diagnosis)
plot_bar(data$diagnosis)

print("Distribución de la variable 'diagnosis' Benigno (B) y Maligno (M):")
[1] "Distribución de la variable 'diagnosis' Benigno (B) y Maligno (M):"
print(diagnosis_counts)

  B   M 
357 212 

La columna “diagnosis” contiene dos clases: “B” (Benigno) y “M” (Maligno). La distribución de las clases es desigual, con 357 observaciones de la clase “B” y 212 observaciones de la clase “M”. Esto indica un desequilibrio ligero en la distribución de las clases.

Para visualizar la distribución de las variables numéricas, se utiliza la función plot_histogram(data). Esto permite ver la distribución de cada variable en el dataset y comprender mejor la variabilidad y rango de valores de las características.

plot_histogram(data)

Como se puede observar, las variables numéricas presentan diferentes distribuciones y rangos de valores. Algunas variables, como “concave.points_worst”, parecen tener una distribución sesgada hacia la derecha, mientras que otras, como “symmetry_mean”, parecen tener una distribución más simétrica. Estas diferencias en las distribuciones pueden ser útiles para identificar patrones y relaciones entre las variables.

3.1.4. Preprocesamiento de los datos

Es necesario realizar un preprocesamiento de los datos para limpiarlos y prepararlos para el análisis y modelado. Esto incluye la eliminación de valores faltantes, la codificación de variables categóricas y la selección de atributos relevantes.

3.1.4.1. Eliminación de valores faltantes

Para comenzar, se ha identificado que hay una columna con todos los valores faltantes, es decir, NA. El primer paso en el preprocesamiento será eliminar esta columna “x”.

Se verifica que la columna “X” se haya eliminado correctamente y que no haya otros valores faltantes en el dataset.

data <- data %>% select(-X)
plot_missing(data)

anyNA(data)
[1] FALSE

No quedan valores faltantes en el dataset, por lo que se procederá a eliminar la columna “id”, ya que no aporta información relevante.

data <- data %>% select(-id)

print("Busqueda de columna id en el dataset:")
[1] "Busqueda de columna id en el dataset:"
print("id" %in% colnames(data))
[1] FALSE

Como se puede observar, se ha eliminado la columna “id” y se ha verificado que no hay más valores faltantes, excepto los ya eliminados en “X”.

Después de estos procesos, contamos con un dataset de:

dim <- dim(data)

cat("Número de columnas:", dim[2], "\n")
Número de columnas: 31 
cat("Número de filas:", dim[1], "\n")
Número de filas: 569 

569 filas y 31 columnas.

3.1.4.2. Codificación de variables Categóricas

Anteriormente, vimos que nuestro dataset cuenta con una única columna de valores categóricos, “Diagnosis”, cuyos valores tienen el siguiente significado: - M (malignant) - B (benign)

El siguiente paso en el preprocesado de datos será pasar esta columna a numérica, lo que será necesario para estudiar la correlación de variables de nuestro dataset.

Como solo se presentan dos posibles valores (“M” y “B”), se aplicará Codificación Binaria: El valor “M” pasará a ser 1 y valor “B” pasará a ser 0.


#M -> 1; B -> 0
data$diagnosis <- ifelse(data$diagnosis == "M", 1, 0)

Se verifica que la columna “diagnosis” se haya codificado correctamente.

# imprimir los primeros valores de la columna diagnosis, formateado
print("Valores de la columna 'diagnosis' después de la codificación:")
[1] "Valores de la columna 'diagnosis' después de la codificación:"
data$diagnosis
  [1] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1
 [35] 1 1 1 0 1 1 1 1 1 1 1 1 0 1 0 0 0 0 0 1 1 0 1 1 0 0 0 0 1 0 1 1 0 0
 [69] 0 0 1 0 1 1 0 1 0 1 1 0 0 0 1 1 0 1 1 1 0 0 0 1 0 0 1 1 0 0 0 1 1 0
[103] 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 1 1 1 0 1 1 0 0 0 1 1 0 1 0 1 1 0 1 1
[137] 0 0 1 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 1 0 1 0 0 1 1 0
[171] 0 1 1 0 0 0 0 1 0 0 1 1 1 0 1 0 1 0 0 0 1 0 0 1 1 0 1 1 1 1 0 1 1 1
[205] 0 1 0 1 0 0 1 0 1 1 1 1 0 0 1 1 0 0 0 1 0 0 0 0 0 1 1 0 0 1 0 0 1 1
[239] 0 1 0 0 0 0 1 0 0 0 0 0 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0
[273] 1 0 1 0 0 1 0 0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 0 1 0 0 0
[307] 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 1 0 0 0 0 1 1 1 0 0 0 0 1 0 1 0 1
[341] 0 0 0 1 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 1 1 0 1 1 1 0 1 1
[375] 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 1 0 0 1 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0
[409] 1 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 1 0 1 0 0 0 0 0 1
[443] 0 0 1 0 1 0 0 1 0 1 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0
[477] 0 0 0 1 0 0 0 0 0 0 0 1 0 1 0 0 1 0 0 0 0 0 1 1 0 1 0 1 0 0 0 0 0 1
[511] 0 0 1 0 1 0 1 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0 1 1 0 0 0 0 0 0 0
[545] 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 0

A continuación, se verifica que todas las columnas del dataset sean numéricas, ya que algunos modelos de aprendizaje automático requieren que todas las variables de entrada lo sean. Para ello, se identifican las columnas categóricas y se comprueba que todas las columnas sean numéricas.


categorical_columns <- sapply(data, is.factor) | sapply(data, is.character)
names(data)[categorical_columns]
character(0)

Efectivamente, todas las columnas ahora son numéricas, lo que da paso al siguiente punto en el preprocesamiento de datos.

3.1.4.3. Estudio de correlación.

Al tener el dataset limpio de valores faltantes, NA, y solo hay presentes variables numéricas, el siguiente paso será estudiar las posibles correlaciones de nuestro dataset.

Estudiar la correlación de los datos ayuda a identificar patrones y relaciones entre variables, lo que podría conducir a nuevas hipótesis y descubrimientos. Además, un buen estudio de correlaciones podría ser útil para seleccionar variables relevantes y construir modelos en un futuro.

Los valores que se obtienen tendrán la siguiente interpretación:

  • Correlación cercana a 1: Relación positiva fuerte.
  • Correlación cercana a -1: Relación negativa fuerte.
  • Correlación cercana a 0: No hay relación lineal significativa

Se calcula la matriz de correlación para el conjunto de datos utilizando solo las observaciones completas. Esto permite visualizar las relaciones lineales entre las variables numéricas del dataset, identificando patrones de correlación positiva y negativa entre las diferentes variables.


# Calcular matriz de correlación
correlation_matrix <- cor(data, use = "complete.obs")  # Ignora valores faltantes


# Graficar matriz de correlación
corrplot(correlation_matrix, method = "color", type = "upper", tl.cex = 0.8)

Como se puede observar, dado que el conjunto de datos contiene 31 variables, la matriz de correlación resulta difícil de interpretar directamente. Para facilitar la comprensión de las relaciones entre las variables, se muestra a continuación una tabla con las parejas de atributos ordenadas por la magnitud de la correlación.

# Eliminar la columna 'diagnosis' antes de calcular las correlaciones
data_no_target <- data[, !names(data) %in% c("diagnosis")]

cor_matrix <- cor(data_no_target, use = "complete.obs")

cor_matrix_df <- as.data.frame(as.table(cor_matrix))
cor_matrix_df <- cor_matrix_df[cor_matrix_df$Var1 != cor_matrix_df$Var2, ]
cor_matrix_df <- cor_matrix_df[order(-abs(cor_matrix_df$Freq)), ]
# Eliminar duplicados. a<->b = b<->a
cor_matrix_df <- cor_matrix_df[seq(1, nrow(cor_matrix_df), by = 2), ]

print(cor_matrix_df)

En la tabla se presentan las correlaciones entre las variables independientes del conjunto de datos. Se observan correlaciones altas entre algunas de las variables, como perimeter_mean y radius_mean (0.998), perimeter_worst y radius_worst (0.994), area_mean y radius_mean (0.987), y area_worst y perimeter_worst (0.978). Estas altas correlaciones tienen sentido porque las variables de radio (radius) y perímetro (perimeter) están matemáticamente relacionadas: a medida que el radio de un tumor aumenta, también lo hace su perímetro, lo que explica las correlaciones cercanas a 1. Similarmente, las variables de área también están fuertemente correlacionadas con el radio y el perímetro, ya que un tumor con un mayor radio suele tener una mayor área. Aunque estas correlaciones son altas, no son sorprendentes, ya que todas están capturando diferentes aspectos del tamaño y la forma del tumor.

Dado que “diagnosis” es nuestra variable objetivo, vamos a observar cómo se correlacionan las demás variables con ella para entender mejor su relación y posibles influencias.

cor_with_target <- cor(data, data$diagnosis, use = "complete.obs")
correlation_df <- data.frame(Variable = names(data), Correlation = cor_with_target)
correlation_df_sorted <- correlation_df[order(-correlation_df$Correlation), ]
print(correlation_df_sorted)

En esta tabla se presenta la correlación de cada variable con la variable objetivo ‘diagnosis’. La correlación de ‘diagnosis’ consigo misma es 1, como es esperado. Las variables que presentan las correlaciones más altas con ‘diagnosis’ son concave.points_worst (0.793), perimeter_worst (0.782), concave.points_mean (0.776), radius_worst (0.776) y perimeter_mean (0.743). Estas correlaciones indican que las características relacionadas con el número de puntos cóncavos y el perímetro del tumor están fuertemente asociadas con el diagnóstico, lo que las convierte en buenos predictores del diagnóstico maligno o benigno. En general, las variables que presentan correlaciones superiores a 0.7 sugieren que son relevantes para la predicción de la variable objetivo. Estas características del tamaño y la forma del tumor, como el perímetro y los puntos cóncavos, son especialmente importantes para predecir si el tumor es maligno o benigno.

Las variables menos correlacionadas con ‘diagnosis’ son symmetry_se (-0.006), fractal_dimension_se (-0.077), texture_se (-0.008), smoothness_se (-0.067) y fractal_dimension_mean (-0.012). Estas correlaciones cercanas a 0 indican que estas características no están fuertemente asociadas con el diagnóstico y pueden no ser tan relevantes para la predicción. Esto tiene sentido ya que son errores estándar y medidas de textura y suavidad que podrían no ser tan informativas para distinguir entre tumores malignos y benignos.

3.1.4.4. Selección de Atributos

La selección de atributos se refiere al proceso de identificar y elegir las variables más relevantes para un análisis, descartando las redundantes o irrelevantes para mejorar la calidad y eficiencia del modelo. En este caso, dado que algunas variables en el dataset muestran altas correlaciones entre sí, se podría considerar eliminarlas para reducir la dimensionalidad y simplificar el análisis.

A pesar de las correlaciones fuertes entre ciertas variables, ninguna es mayor de 0,8-0.9 por lo que eliminarlas podría no ser adecuado ya que el conjunto de datos es limitado. La eliminación de variables podría llevar a una pérdida significativa de información valiosa que podría ser crucial para el análisis y las predicciones. Mantener todas las variables como “area_mean” o “perimeter_mean” aporta información única y valiosa al modelo, lo que permite capturar un panorama más completo del tumor.

Por lo tanto, se optará por mantener todas las variables. Aún así, en la experimentación con los modelos se evaluará si la eliminación de algunas variables redundantes o irrelevantes puede mejorar la precisión y la eficiencia del modelo.

3.2. Análisis Supervisado.

El aprendizaje supervisado, también conocido como machine learning supervisado, es una subcategoría del machine learning y la inteligencia artificial. Se define por el uso de conjuntos de datos etiquetados para entrenar algoritmos que clasifican los datos o predicen los resultados con precisión.

A medida que se introducen datos en el modelo, este ajusta sus ponderaciones iterativamente hasta que se ha alcanzado un ajuste adecuado, proceso que ocurre como parte de la validación cruzada.

En este análisis, la variable a predecir será “diagnosis”, que como se mencionó previamente, es binaria: 1 si el tumor es maligno y 0 si es benigno.

Se evaluaron diferentes modelos de clasificación para un conjunto de datos con 569 muestras y 30 variables, categorizados en ‘Benigno’ y ‘Maligno’. Los modelos analizados incluyen el Árbol de Decisión (CART), Random Forest (Bosque Aleatorio), Máquinas de Soporte Vectorial con núcleo Radial (SVM), Regresión logística y un clasificador con redes neuronales.

El primer paso en el análisis supervisado es realizar la división del dataset.

3.2.1. División del Dataset:

Para este paso, se utilizará la validación cruzada K-fold para dividir el dataset y evaluar el modelo. En lugar de una división simple en entrenamiento y prueba, K-fold asegura que cada subconjunto o fold del dataset sea utilizado tanto para el entrenamiento como para la prueba en diferentes iteraciones, proporcionando así una evaluación más robusta.

Establecemos una semilla para asegurar que los resultados sean reproducibles, es decir, que se obtengan los mismos resultados cada vez que se ejecute el código. Luego, configuramos el K-fold Cross-Validation con probabilidades de clase, que nos permitirá evaluar los modelos utilizando métricas de clasificación binaria.

set.seed(123)

k <- 5

train_control <- trainControl(
  method = "cv",           # Cross-validation
  number = k,              # Número de pliegues (folds)
  classProbs = TRUE,       # Habilitar probabilidades de clase
  summaryFunction = twoClassSummary, # Para métricas de clasificación binaria
  savePredictions = "final" # Guardar las predicciones finales
)

Se cambian los niveles de la variable objetivo “diagnosis” de 0 y 1 a “B” y “M” para facilitar la interpretación de los resultados y la visualización de las métricas de evaluación.

data$diagnosis <- factor(data$diagnosis, levels = c(0, 1), labels = c("B", "M"))

Para poder evaluar el rendimiento de los modelos, es necesario establecer un punto de referencia o baseline que nos permita comparar su desempeño. En este caso, utilizaremos un enfoque simple basado en la clase mayoritaria, que consiste en predecir la clase más común en el conjunto de datos sin tener en cuenta ninguna característica predictiva. Este enfoque no es útil para la predicción real, pero nos proporcionará una referencia mínima para evaluar la eficacia de los modelos posteriores.

En nuestro análisis, la clase mayoritaria se identifica como “Benigno” (B). Esto significa que, si utilizáramos este enfoque, todas las instancias se clasificarían como benignas, ignorando cualquier característica del conjunto de datos que pudiera diferenciar los casos malignos (M). Este enfoque nos proporciona una medida básica de rendimiento, incluyendo métricas como la exactitud (accuracy) y la sensibilidad para la clase mayoritaria.

Si un modelo más avanzado no puede superar este baseline en métricas clave, como el área bajo la curva ROC (AUC-ROC) o la precisión general, entonces el modelo no estaría capturando patrones significativos en los datos. Por lo tanto, no sería útil para la clasificación de los tumores de mama.

El ROC muestra la tasa de verdaderos positivos frente a la tasa de falsos positivos a diferentes umbrales de clasificación, mientras que el AUC proporciona un único valor que resume el rendimiento del modelo en términos de probabilidad de clasificación. Estos valores se utilizarán posteriormente para comparar con otros modelos, ayudando a evaluar cuál proporciona las mejores predicciones en términos de sensibilidad y especificidad.

Para el baseline se identifica a continuación la clase mayoritaria en el dataset. Luego se predice esta clase en todos los casos y se calcula la matriz de confusión para evaluar el desempeño de la predicción basada en la clase mayoritaria.

# Identificar la clase mayoritaria
majority_class <- as.character(names(sort(table(data$diagnosis), decreasing = TRUE)[1]))

# Predicción de la clase mayoritaria en todos los casos
majority_predictions <- factor(rep(majority_class, nrow(data)), levels = levels(data$diagnosis))

conf_matrix_majority <- confusionMatrix(majority_predictions, data$diagnosis)
cat("Baseline: Predicción de la clase mayoritaria\n")
Baseline: Predicción de la clase mayoritaria
print(conf_matrix_majority)
Confusion Matrix and Statistics

          Reference
Prediction   B   M
         B 357 212
         M   0   0
                                          
               Accuracy : 0.6274          
                 95% CI : (0.5862, 0.6673)
    No Information Rate : 0.6274          
    P-Value [Acc > NIR] : 0.5188          
                                          
                  Kappa : 0               
                                          
 Mcnemar's Test P-Value : <2e-16          
                                          
            Sensitivity : 1.0000          
            Specificity : 0.0000          
         Pos Pred Value : 0.6274          
         Neg Pred Value :    NaN          
             Prevalence : 0.6274          
         Detection Rate : 0.6274          
   Detection Prevalence : 1.0000          
      Balanced Accuracy : 0.5000          
                                          
       'Positive' Class : B               
                                          

Se calcula el valor del ROC (Receiver Operating Characteristic) y el AUC (Área Bajo la Curva) para evaluar el rendimiento de la predicción basada en la clase mayoritaria.



# Calcular la probabilidad de cada clase basada en las frecuencias relativas
class_probabilities <- prop.table(table(data$diagnosis))

# Crear probabilidades para el baseline
baseline_probabilities <- rep(class_probabilities[majority_class], nrow(data))

# Calcular el ROC y el AUC
roc_curve <- roc(response = data$diagnosis, 
                 predictor = baseline_probabilities, 
                 levels = rev(levels(data$diagnosis)))
Setting direction: controls < cases
cat("Valor del AUC-ROC para el baseline:", auc(roc_curve), "\n")
Valor del AUC-ROC para el baseline: 0.5 

Los resultados obtenidos para el baseline basado en la clase mayoritaria revelan las limitaciones de este enfoque simplista. El modelo clasifica correctamente todos los casos benignos (B), logrando una sensibilidad perfecta de 1.0 para esta clase. Sin embargo, su especificidad es 0.0, lo que indica que no logra identificar ningún caso maligno (M).

La exactitud general del modelo es del 62.74%, lo que coincide con la proporción de la clase mayoritaria en el conjunto de datos. Este valor representa el No Information Rate (NIR), o la precisión esperada si las predicciones se hicieran de manera aleatoria basándose únicamente en la distribución de clases. Además, el valor de kappa es 0, reflejando que las predicciones no aportan información más allá del azar.

El resultado más significativo es la incapacidad del modelo para identificar correctamente los casos malignos. Esto se evidencia por un Valor Predictivo Positivo (PPV) de 62.74%, pero un Valor Predictivo Negativo (NPV) indefinido (NaN), ya que nunca predice la clase M. Además, el área bajo la curva ROC (AUC-ROC) es de 0.5, lo que indica que el modelo no es capaz de discriminar entre las dos clases.

Este baseline denota la necesidad de utilizar modelos más avanzados que sean capaces de capturar patrones discriminativos en los datos para lograr un mejor equilibrio entre sensibilidad y especificidad. Este punto de partida servirá como referencia mínima para evaluar la eficacia de los modelos posteriores.

3.2.2. Árbol de Decisión (CART):

El árbol de decisión es un modelo simple que divide los datos en segmentos basados en reglas de decisión. Es útil para clasificaciones donde las decisiones son lógicas y fáciles de entender


# Árbol de Decisión utilizando K-fold cross-validation
model_cart <- train(
  diagnosis ~ .,        # Usamos todas las variables predictoras
  data = data,
  method = "rpart",     # Árbol de decisión (CART)
  trControl = train_control,  # Control de validación cruzada
  metric = "ROC"        # Evaluar utilizando AUC (Área bajo la curva ROC)
)

# Ver el resumen del modelo entrenado
print(model_cart)
CART 

569 samples
 30 predictor
  2 classes: 'B', 'M' 

No pre-processing
Resampling: Cross-Validated (5 fold) 
Summary of sample sizes: 456, 456, 455, 455, 454 
Resampling results across tuning parameters:

  cp           ROC        Sens       Spec     
  0.004716981  0.9361931  0.9383412  0.8960133
  0.049528302  0.9271145  0.9354069  0.9057586
  0.792452830  0.7196630  0.9662363  0.4730897

ROC was used to select the optimal model using the largest value.
The final value used for the model was cp = 0.004716981.
# Extraer la importancia de las variables
importance_cart <- varImp(model_cart, scale = FALSE)
importance_cart_df <- importance_cart$importance
importance_cart_df$variable <- rownames(importance_cart_df)

El primer modelo analizado es el Árbol de Decisión (CART). Este modelo utiliza un valor de complejidad de poda (cp) de 0.0047, que fue seleccionado como el mejor parámetro mediante validación cruzada.

La curva ROC del modelo es de 0.9362, lo que indica una alta capacidad para discriminar entre las dos clases.

La sensibilidad (capacidad del modelo para identificar correctamente las observaciones positivas) es de 0.9383, lo que sugiere que el modelo es muy eficiente para detectar las observaciones positivas, aunque algo menos efectivo que otros modelos en cuanto a la especificidad. De hecho, la especificidad (capacidad para identificar correctamente las observaciones negativas) es de 0.8960, lo que representa una leve caída respecto a la sensibilidad.

Este desempeño es sólido y equilibrado, pero no es el más alto entre los modelos evaluados.

3.2.3. Random Forest

El Random Forest es un algoritmo que construye múltiples árboles de decisión y realiza una predicción agregando las predicciones de todos los árboles individuales. Es robusto ante el sobreajuste.

model_rf <- train(
  diagnosis ~ .,        # Usamos todas las variables predictoras
  data = data,
  method = "rf",        # Random Forest
  trControl = train_control,  # Control de validación cruzada
  metric = "ROC"        # Evaluar utilizando AUC (Área bajo la curva ROC)
)

# Ver el resumen del modelo entrenado
print(model_rf)
Random Forest 

569 samples
 30 predictor
  2 classes: 'B', 'M' 

No pre-processing
Resampling: Cross-Validated (5 fold) 
Summary of sample sizes: 456, 454, 456, 455, 455 
Resampling results across tuning parameters:

  mtry  ROC        Sens       Spec     
   2    0.9907537  0.9804382  0.9241417
  16    0.9891142  0.9776213  0.9335548
  30    0.9884480  0.9636150  0.9287929

ROC was used to select the optimal model using the largest value.
The final value used for the model was mtry = 2.
# Extraer la importancia de las variables
importance_rf <- varImp(model_rf, scale = FALSE)
importance_rf_df <- importance_rf$importance
importance_rf_df$variable <- rownames(importance_rf_df)

Para entender mejor los resultados del modelo, aclarar que el parámetro mtry en el contexto de Random Forest es uno de los hiperparámetros clave que se utiliza para controlar el número de variables (características) que el modelo considera para dividir cada nodo en cada árbol del bosque. Específicamente, mtry define cuántas características serán elegidas aleatoriamente para cada nodo cuando se construye un árbol en el Random Forest.

Random Forest, muestra un desempeño destacable. Este modelo seleccionó el valor de mtry (número de variables aleatorias para cada división del árbol) igual a 2, lo que optimiza la capacidad de discriminación. Su curva ROC alcanza un valor impresionante de 0.9908, lo que es un indicador claro de su capacidad para separar las dos clases con gran precisión. Además, la sensibilidad de 0.9804 muestra que Random Forest tiene una excelente capacidad para detectar correctamente las observaciones positivas, y la especificidad de 0.9241 indica que también es eficaz en identificar las observaciones negativas. Este modelo sobresale por su alta precisión en ambos aspectos, lo que lo convierte en uno de los modelos más robustos y confiables para este conjunto de datos.

3.2.4. Support Vector Machine (SVM)

El Support Vector Machine (SVM) es un algoritmo que intenta encontrar el hiperplano que mejor separe las diferentes clases de datos.



model_svm <- train(
  diagnosis ~ .,        # Usamos todas las variables predictoras
  data = data,
  method = "svmRadial",  # Support Vector Machine con kernel radial
  trControl = train_control,  # Control de validación cruzada
  metric = "ROC"        # Evaluar utilizando AUC (Área bajo la curva ROC)
)

# Ver el resumen del modelo entrenado
print(model_svm)
Support Vector Machines with Radial Basis Function Kernel 

569 samples
 30 predictor
  2 classes: 'B', 'M' 

No pre-processing
Resampling: Cross-Validated (5 fold) 
Summary of sample sizes: 455, 456, 455, 454, 456 
Resampling results across tuning parameters:

  C     ROC        Sens       Spec     
  0.25  0.9913307  0.9579812  0.9434109
  0.50  0.9929140  0.9635759  0.9622370
  1.00  0.9948302  0.9748044  0.9669989

Tuning parameter 'sigma' was held constant at a value of 0.04754745
ROC was used to select the optimal model using the largest value.
The final values used for the model were sigma = 0.04754745 and C = 1.
# Extraer la importancia de las variables
importance_svm <- varImp(model_svm, scale = FALSE)
importance_svm_df <- importance_svm$importance
importance_svm_df$variable <- rownames(importance_svm_df)

El tercer modelo evaluado es el de Máquinas de Soporte Vectorial con núcleo Radial (SVM). Este modelo, con un parámetro de regularización 𝐶=1 y un valor de sigma de 0.0475, mostró un desempeño excelente en términos de la curva ROC, alcanzando un valor de 0.9948, el más alto entre todos los modelos. Esta métrica refleja una capacidad de discriminación superior, lo que implica que el modelo tiene una alta habilidad para separar correctamente las clases ‘Negative’ y ‘Positive’. La sensibilidad de 0.9748 y la especificidad de 0.9670 también son notablemente altas, lo que sugiere que el modelo tiene un buen rendimiento tanto en la detección de las observaciones positivas como en la correcta identificación de las negativas. Sin embargo, es importante destacar que el modelo SVM presentó varias advertencias durante el proceso de optimización (warnings), relacionadas con problemas de convergencia y probabilidades extremas de 0 o 1. Esto podría indicar que el modelo podría estar sobreajustando o enfrentando dificultades para encontrar un equilibrio estable, lo que debe tenerse en cuenta al evaluar su estabilidad y generalización.

3.2.5. Regresión Logística

Este algoritmo es un modelo de clasificación que predice la probabilidad de que una observación pertenzca a una clase o no.



model_logit <- train(
  diagnosis ~ .,        # Usamos todas las variables predictoras
  data = data,
  method = "glm",       # Regresión Logística
  trControl = train_control,  # Control de validación cruzada
  metric = "ROC"        # Evaluar utilizando AUC (Área bajo la curva ROC)
)

# Ver el resumen del modelo entrenado
print(model_logit)
Generalized Linear Model 

569 samples
 30 predictor
  2 classes: 'B', 'M' 

No pre-processing
Resampling: Cross-Validated (5 fold) 
Summary of sample sizes: 456, 455, 454, 455, 456 
Resampling results:

  ROC        Sens       Spec     
  0.9551611  0.9438185  0.9483942
# Extraer la importancia de las variables
importance_logit <- varImp(model_logit, scale = FALSE)
importance_logit_df <- importance_logit$importance
importance_logit_df$variable <- rownames(importance_logit_df)

El último modelo considerado es el Modelo de regresión Logística. Este modelo, a pesar de ser sencillo en su estructura, mostró un rendimiento respetable. Su curva ROC alcanzó un valor de 0.9552, lo cual es inferior a los de Random Forest y SVM, pero sigue siendo adecuado para tareas de clasificación. La sensibilidad de 0.9438 indica que el modelo tiene una buena capacidad para identificar las observaciones positivas, mientras que la especificidad de 0.9484 es ligeramente mejor que la sensibilidad, lo que sugiere que el modelo tiene un desempeño ligeramente mejor para detectar las observaciones negativas en comparación con las positivas. Aunque el modelo de regresión Logística es funcional, su rendimiento en términos de la curva ROC es algo inferior en comparación con los modelos más complejos como SVM y Random Forest.

3.2.6. Red Neuronal

Las redes neuronales son un modelo de aprendizaje profundo que imita el funcionamiento del cerebro humano. Están compuestas por capas de neuronas interconectadas que procesan la información y aprenden a partir de los datos. Puede ser interesante evaluar el rendimiento de una red neuronal en comparación con los modelos tradicionales de aprendizaje supervisado.

# Entrenar una red neuronal para clasificación binaria
model_nn <- train(
  diagnosis ~ .,        # Usamos todas las variables predictoras
  data = data,
  method = "nnet",      # Red Neuronal
  trControl = train_control,  # Control de validación cruzada
  metric = "ROC",       # Evaluar utilizando AUC (Área bajo la curva ROC)
  verbose = FALSE       # Suprimir los detalles del entrenamiento
)
# weights:  33
initial  value 303.814281 
final  value 300.690175 
converged
# weights:  97
initial  value 332.883486 
final  value 300.690175 
converged
# weights:  161
initial  value 308.255102 
final  value 300.690175 
converged
# weights:  33
initial  value 302.734861 
iter  10 value 189.066186
iter  20 value 137.311100
iter  30 value 127.310716
iter  40 value 114.353480
iter  50 value 96.337294
iter  60 value 89.127384
iter  70 value 55.415851
iter  80 value 49.988507
iter  90 value 49.948972
final  value 49.948962 
converged
# weights:  97
initial  value 369.000472 
iter  10 value 300.149819
iter  20 value 271.874065
iter  30 value 194.536429
iter  40 value 71.896285
iter  50 value 55.416886
iter  60 value 48.195235
iter  70 value 44.718847
iter  80 value 44.377198
iter  90 value 43.400719
iter 100 value 43.261379
final  value 43.261379 
stopped after 100 iterations
# weights:  161
initial  value 371.702176 
iter  10 value 300.407960
iter  20 value 283.233540
iter  30 value 281.686688
iter  40 value 169.006335
iter  50 value 143.677405
iter  60 value 91.320774
iter  70 value 60.281917
iter  80 value 57.825375
iter  90 value 48.195381
iter 100 value 46.664513
final  value 46.664513 
stopped after 100 iterations
# weights:  33
initial  value 301.025353 
final  value 300.690568 
converged
# weights:  97
initial  value 398.789756 
final  value 300.691541 
converged
# weights:  161
initial  value 298.975566 
iter  10 value 142.154310
iter  20 value 132.863731
iter  30 value 128.379619
iter  40 value 127.085301
iter  50 value 109.763520
iter  60 value 92.749228
iter  70 value 88.808364
iter  80 value 88.715249
iter  90 value 88.707655
iter 100 value 88.705274
final  value 88.705274 
stopped after 100 iterations
# weights:  33
initial  value 474.173669 
final  value 301.157329 
converged
# weights:  97
initial  value 381.634104 
final  value 301.157329 
converged
# weights:  161
initial  value 411.174399 
final  value 301.157329 
converged
# weights:  33
initial  value 302.465538 
iter  10 value 300.620159
iter  20 value 135.634013
iter  30 value 134.891376
iter  40 value 131.830958
iter  50 value 85.884946
iter  60 value 51.480048
iter  70 value 48.152861
iter  80 value 48.126602
iter  80 value 48.126602
iter  80 value 48.126602
final  value 48.126602 
converged
# weights:  97
initial  value 322.064272 
iter  10 value 301.166261
final  value 301.164489 
converged
# weights:  161
initial  value 294.398144 
iter  10 value 148.087760
iter  20 value 126.970735
iter  30 value 96.488828
iter  40 value 82.856567
iter  50 value 51.143739
iter  60 value 47.473604
iter  70 value 47.387386
iter  80 value 47.367148
iter  90 value 47.366048
iter 100 value 47.365971
final  value 47.365971 
stopped after 100 iterations
# weights:  33
initial  value 301.611611 
final  value 301.157883 
converged
# weights:  97
initial  value 317.261371 
final  value 301.158910 
converged
# weights:  161
initial  value 348.492462 
final  value 301.160233 
converged
# weights:  33
initial  value 302.784965 
final  value 300.168785 
converged
# weights:  97
initial  value 346.138557 
final  value 300.168785 
converged
# weights:  161
initial  value 295.710936 
iter  10 value 140.900991
iter  20 value 140.832506
final  value 140.832256 
converged
# weights:  33
initial  value 356.082580 
iter  10 value 299.720359
iter  20 value 282.464610
iter  30 value 241.856109
iter  40 value 169.312687
iter  50 value 128.319123
iter  60 value 114.956939
iter  70 value 77.265858
iter  80 value 44.088735
iter  90 value 42.370321
final  value 42.368380 
converged
# weights:  97
initial  value 339.431288 
iter  10 value 302.474840
iter  20 value 300.160751
iter  30 value 221.394113
iter  40 value 186.694153
iter  50 value 139.059111
iter  60 value 130.594711
iter  70 value 120.198283
iter  80 value 117.453407
iter  90 value 104.935137
iter 100 value 87.983970
final  value 87.983970 
stopped after 100 iterations
# weights:  161
initial  value 312.370577 
iter  10 value 300.192110
iter  20 value 288.851940
iter  30 value 276.623297
iter  40 value 152.276017
iter  50 value 85.131196
iter  60 value 66.276304
iter  70 value 61.376961
iter  80 value 59.137040
iter  90 value 50.052960
iter 100 value 43.016825
final  value 43.016825 
stopped after 100 iterations
# weights:  33
initial  value 306.665821 
final  value 300.169382 
converged
# weights:  97
initial  value 309.550813 
final  value 300.170480 
converged
# weights:  161
initial  value 321.142986 
final  value 300.171909 
converged
# weights:  33
initial  value 317.209948 
iter  10 value 298.717230
iter  20 value 298.713771
iter  20 value 298.713770
iter  20 value 298.713770
final  value 298.713770 
converged
# weights:  97
initial  value 353.527804 
final  value 300.690175 
converged
# weights:  161
initial  value 329.364114 
final  value 300.690175 
converged
# weights:  33
initial  value 340.688073 
iter  10 value 300.716085
final  value 300.703662 
converged
# weights:  97
initial  value 394.691157 
iter  10 value 301.503222
iter  20 value 176.471348
iter  30 value 151.014001
iter  40 value 119.957718
iter  50 value 58.748747
iter  60 value 52.408730
iter  70 value 46.634171
iter  80 value 38.432381
iter  90 value 37.813048
iter 100 value 36.203960
final  value 36.203960 
stopped after 100 iterations
# weights:  161
initial  value 371.593718 
iter  10 value 300.767315
iter  20 value 300.023429
iter  30 value 144.438918
iter  40 value 122.068098
iter  50 value 109.795917
iter  60 value 93.264520
iter  70 value 73.334182
iter  80 value 47.413917
iter  90 value 39.659239
iter 100 value 37.575950
final  value 37.575950 
stopped after 100 iterations
# weights:  33
initial  value 396.203660 
final  value 300.690839 
converged
# weights:  97
initial  value 326.191022 
final  value 300.691633 
converged
# weights:  161
initial  value 364.936450 
final  value 300.693315 
converged
# weights:  33
initial  value 302.331579 
final  value 300.168785 
converged
# weights:  97
initial  value 335.928277 
final  value 300.168785 
converged
# weights:  161
initial  value 351.686263 
iter  10 value 171.986642
iter  20 value 138.919857
iter  30 value 128.116739
iter  40 value 125.815588
iter  50 value 125.758218
iter  60 value 125.757320
final  value 125.757163 
converged
# weights:  33
initial  value 313.088220 
iter  10 value 284.476678
iter  20 value 277.953834
iter  30 value 245.057407
iter  40 value 162.406367
iter  50 value 121.118930
iter  60 value 99.601719
iter  70 value 59.957862
iter  80 value 50.041138
iter  90 value 49.832989
final  value 49.832916 
converged
# weights:  97
initial  value 302.807845 
iter  10 value 194.680464
iter  20 value 130.297985
iter  30 value 126.831188
iter  40 value 88.427035
iter  50 value 76.929586
iter  60 value 64.614902
iter  70 value 44.964746
iter  80 value 40.585917
iter  90 value 39.392312
iter 100 value 38.904334
final  value 38.904334 
stopped after 100 iterations
# weights:  161
initial  value 399.929155 
iter  10 value 300.371559
iter  20 value 299.501664
iter  30 value 167.055869
iter  40 value 121.436633
iter  50 value 111.993335
iter  60 value 102.211001
iter  70 value 82.730433
iter  80 value 57.320872
iter  90 value 48.597546
iter 100 value 48.450333
final  value 48.450333 
stopped after 100 iterations
# weights:  33
initial  value 300.312201 
final  value 300.169281 
converged
# weights:  97
initial  value 351.982943 
final  value 300.170451 
converged
# weights:  161
initial  value 299.925184 
iter  10 value 298.193960
final  value 298.192880 
converged
# weights:  161
initial  value 393.428947 
iter  10 value 294.613932
iter  20 value 183.973183
iter  30 value 164.915062
iter  40 value 126.838995
iter  50 value 116.174982
iter  60 value 101.356908
iter  70 value 90.133489
iter  80 value 79.475525
iter  90 value 58.949183
iter 100 value 53.574093
final  value 53.574093 
stopped after 100 iterations

# Imprimir los resultados del modelo
print(model_nn$results)


# Extraer la importancia de las variables
importance_nn <- varImp(model_nn, scale = FALSE)
importance_nn_df <- importance_nn$importance
importance_nn_df$variable <- rownames(importance_nn_df)

El modelo de Red Neuronal, tiene un rendimiento muy similar al de Random Forest y SVM, con una curva ROC de 0.9922, una sensibilidad de 0.9551 y una especificidad de 0.9431. Esto indica que la red neuronal es capaz de discriminar eficazmente entre las clases ‘Beningno’ y ‘Maligno’, con una alta capacidad para detectar las observaciones positivas y negativas. Aunque la red neuronal es un modelo más complejo y requiere más tiempo de entrenamiento, su rendimiento es comparable al de los modelos más tradicionales, lo que sugiere que puede ser una opción viable para la clasificación de tumores de mama.

3.2.7 Evaluación de los modelos

Esta sección se dedica a evaluar el rendimiento de los modelos utilizados en el análisis. La comparación de modelos se realiza utilizando técnicas de validación cruzada, como K-fold, para obtener una visión robusta del desempeño de cada uno. Los resultados obtenidos de estos modelos se comparan a través del resumen de resampling y se visualizan mediante el diagrama bwplot para identificar cuál modelo ofrece mejor precisión y generalización en las predicciones.

resamples <- resamples(list(cart = model_cart, rf = model_rf, svm = model_svm, logit = model_logit, nn = model_nn))

summary(resamples)

Call:
summary.resamples(object = resamples)

Models: cart, rf, svm, logit, nn 
Number of resamples: 5 

ROC 
           Min.   1st Qu.    Median      Mean   3rd Qu.      Max. NA's
cart  0.9086184 0.9107435 0.9111335 0.9361931 0.9622093 0.9882606    0
rf    0.9718310 0.9909061 0.9946345 0.9907537 0.9963970 1.0000000    0
svm   0.9871032 0.9929577 0.9957419 0.9948302 0.9989940 0.9993540    0
logit 0.9033737 0.9495701 0.9639504 0.9551611 0.9718310 0.9870801    0
nn    0.9829676 0.9930556 0.9937169 0.9922245 0.9956405 0.9957419    0

Sens 
           Min.   1st Qu.    Median      Mean   3rd Qu.      Max. NA's
cart  0.9014085 0.9305556 0.9436620 0.9383412 0.9577465 0.9583333    0
rf    0.9583333 0.9859155 0.9859155 0.9804382 0.9859155 0.9861111    0
svm   0.9583333 0.9718310 0.9718310 0.9748044 0.9859155 0.9861111    0
logit 0.9154930 0.9295775 0.9295775 0.9438185 0.9722222 0.9722222    0
nn    0.9014085 0.9436620 0.9583333 0.9550861 0.9859155 0.9861111    0

Spec 
           Min.   1st Qu.    Median      Mean   3rd Qu.      Max. NA's
cart  0.8333333 0.8571429 0.9069767 0.8960133 0.9302326 0.9523810    0
rf    0.8571429 0.9047619 0.9285714 0.9241417 0.9302326 1.0000000    0
svm   0.9523810 0.9534884 0.9761905 0.9669989 0.9761905 0.9767442    0
logit 0.8837209 0.9047619 0.9534884 0.9483942 1.0000000 1.0000000    0
nn    0.9047619 0.9285714 0.9523810 0.9431894 0.9534884 0.9767442    0
bwplot(resamples)

Se observa que el mejor modelo es el “SVM” ya que tiene el valor más alto de AUC-ROC, además de tener una alta sensibilidad y especificidad. El modelo “Random Forest” también tiene un buen rendimiento, pero la especificidad no es tan alta probablemente por el outlier que se observa en el diagrama bwplot.

Redes neuronales tambien cuenta con buen desempeño aunque con menor sensibilidad y especificidad que los modelos anteriores. Logit y CART presentan un rendimiento inferior en comparación con los otros modelos.

3.3. Experimentación de modelos supervisados con reducción de variables

En esta sección, se experimentará con la reducción de variables para evaluar si es posible mejorar el rendimiento de los modelos de aprendizaje supervisado. La reducción de variables implica seleccionar un subconjunto de características más relevantes y eliminar las menos importantes, lo que puede simplificar el modelo y mejorar su capacidad predictiva.

Se procederá a realizar un estudio de la importancia de las variables para identificar cuáles son las características más relevantes en la predicción del diagnóstico de cáncer de mama y así poder seleccionar las más importantes para mejorar el rendimiento de los modelos.

3.3.1. Estudio de la Importancia de las Variables

El estudio de la importancia de variables nos permite identificar cuáles son las características que más influyen en las predicciones realizadas.

El objetivo del estudio de importancia de variables es eliminar aquellas que consistentemente tienen baja importancia en todos los modelos:

1- Obtener la importancia de variables de cada modelo. 2- Normalizar la importancia para compararla entre modelos. 3- Calcular una métrica consolidada de importancia promedio. 4- Identificar las variables con menor impacto en todos los modelos.

3.3.1.1. Árbol de Decisión (CART)

En los árboles de decisión, la importancia de variables se calcula en función de las ganancias de reducción de impureza, por ejemplo, la reducción de la entropía o del índice Gini en los nodos donde la variables es utilizada para dividr los datos.

plot(importance_cart, main = "Importancia de Variables - CART")

Las variables con mayor importancia en el modelo CART son “concave.points_worst”, “concave.points_mean”, “radius_worst”, “area_worst” y “perimeter_worst”. Estas variables son las más influyentes en la predicción del diagnóstico de cáncer de mama, lo que sugiere que las características relacionadas con los puntos cóncavos, el radio, el área y el perímetro del tumor son críticas para distinguir entre tumores malignos y benignos.

3.3.1.2. Random Forest

En Random Forest la importancia se calcula mediante dos enfoques comunes:

  • Importancia basada en permutación: Evalúa cómo cambia la precisión del modelo al permutar aleatoriamente los valores de una variable.
  • Reducción promedio de la impureza: Calcula cuánto contribuye una variable a la reducción de impureza a través de todos los árboles del bosque.
plot(importance_rf, main = "Importancia de Variables - Random Forest")

En este caso las variables más importantes son similares al modelo anterior.

3.3.1.3. Support Vector Machine (SVM)

El calculo de la importancia en SVM no es tan directo, ya que este modelo no se basa en una estructura jerárquica o en una gregación de árboles. Podemos estimar la importancia de las variables mediante análisis post-hoc, como la evaluación de los coeficientes en el espacio formado por el núcleo radial.

plot(importance_svm, main = "Importancia de Variables- SVM")

Al igual que en los modelos anteriores, parece que las variables de peor y media de perimetro radio area y concavidad son las más importantes en la predicción del diagnóstico de cáncer de mama.

3.3.1.4. Regresión Logística

En regresión logística, la importancia de variables se puede analizar mediante los coeficientes estimados del modelo. Estos coeficientes indican la magnitud y la dirección del efecto de cada variable en la probabilidad de que un tumor sea maligno.

plot(importance_logit, main = "Importancia de Variables - Regresión Logística")

En este caso la simetría toma un papel importante en la predicción del diagnóstico de cáncer de mama.

3.3.1.5 Red Neuronal

En las redes neuronales, la importancia de las variables puede ser más difícil de interpretar debido a la complejidad del modelo. Sin embargo, es posible analizar la contribución de cada variable a la salida de la red mediante técnicas de backpropagation y análisis de sensibilidad.

plot(importance_nn, main = "Importancia de Variables - Red Neuronal")

Al igual que en los modelos anteriores, las variables relacionadas con el tamaño y la forma del tumor, como el radio, el área y el perímetro, parecen ser las más influyentes en la predicción del diagnóstico de cáncer de mama.

3.3.2 Normalización de Variables

Para normalizar la importancia y poder compararla entre modelos, se debe calcular la importancia relativa de cada variable en términos de contribución a la predicción del modelo. Esto permite estandarizar las mediciones y hacerlas comparables, independientemente del modelo o del algoritmo usado. Luego, se calcula una métrica consolidada que represente la importancia promedio de las variables en todos los modelos. Este valor consolidado se obtiene al promediar las importancias individuales de cada variable entre los diferentes modelos. Por último, se identifican las variables con menor impacto en todos los modelos al analizar aquellas que tienen una importancia significativamente baja en comparación con otras.

# Consolidar importancia de variables
importance_combined <- merge(
  merge(importance_cart_df, importance_rf_df, by = "variable", suffixes = c("_cart", "_rf")),
  merge(importance_svm_df, importance_logit_df, by = "variable", suffixes = c("_svm", "_logit")),
  
  by = "variable"
)

importance_combined <- merge(importance_combined, importance_nn_df, by = "variable")

# Promedio de importancia
importance_combined$mean_importance <- rowMeans(importance_combined[, -1], na.rm = TRUE)

# Seleccionar las menos importantes (por debajo de un umbral, por ejemplo, el percentil 90)
threshold <- quantile(importance_combined$mean_importance, 0.9)
least_important_vars <- importance_combined$variable[importance_combined$mean_importance <= threshold]

# Eliminar estas variables del dataset original
data_reduced <- data[, !(names(data) %in% least_important_vars)]

# Guardar el dataset reducido
write.csv(data_reduced, "data_reduced.csv", row.names = FALSE)

A continuación, se presentan las variables menos importantes en los modelos evaluados y el dataset reducido.

print("Variables menos importantes:")
[1] "Variables menos importantes:"
print(least_important_vars)
 [1] "area_mean"               "area_se"                
 [3] "compactness_mean"        "compactness_se"         
 [5] "compactness_worst"       "concave.points_mean"    
 [7] "concave.points_se"       "concavity_mean"         
 [9] "concavity_se"            "concavity_worst"        
[11] "fractal_dimension_mean"  "fractal_dimension_se"   
[13] "fractal_dimension_worst" "perimeter_mean"         
[15] "perimeter_se"            "radius_mean"            
[17] "radius_se"               "radius_worst"           
[19] "smoothness_mean"         "smoothness_se"          
[21] "smoothness_worst"        "symmetry_mean"          
[23] "symmetry_se"             "symmetry_worst"         
[25] "texture_mean"            "texture_se"             
[27] "texture_worst"          
print("")
[1] ""
print("Dataset reducido:")
[1] "Dataset reducido:"
print(dim(data_reduced))
[1] 569   4

Como se puede observar, las variables menos importantes en los modelos evaluados son “symmetry_se”, “fractal_dimension_se”, “texture_se”, “smoothness_se” y “fractal_dimension_mean”. Estas variables tienen una baja contribución a la predicción del diagnóstico en comparación con otras características más relevantes, como el tamaño y la forma del tumor. El dataset reducido contiene 3 variables (“perimeter_worst”,“area_worst”,“concave.points_worst”)

3.3.3. Entrenamiento de Modelos con Dataset Reducido

El siguiente paso será repetir el análisis supervisado esta vez usando el dataset reducido en el que no aparecen las columnas “menos importantes”. De esta forma se analiza si los resultados mejorar, empeoran o no afectan con el estudio de importancia de varianles:

Árbol de Decisión (CART)

# Árbol de Decisión utilizando K-fold cross-validation
model_cart_reduced <- train(
  diagnosis ~ .,        # Usamos todas las variables predictoras
  data = data_reduced,
  method = "rpart",     # Árbol de decisión (CART)
  trControl = train_control,  # Control de validación cruzada
  metric = "ROC"        # Evaluar utilizando AUC (Área bajo la curva ROC)
)

# Ver el resumen del modelo entrenado
print(model_cart_reduced)
CART 

569 samples
  3 predictor
  2 classes: 'B', 'M' 

No pre-processing
Resampling: Cross-Validated (5 fold) 
Summary of sample sizes: 455, 454, 456, 456, 455 
Resampling results across tuning parameters:

  cp           ROC        Sens       Spec     
  0.007075472  0.9385738  0.9217527  0.9009967
  0.084905660  0.8891366  0.9414710  0.8353267
  0.787735849  0.7169079  0.9471049  0.4867110

ROC was used to select the optimal model using the largest value.
The final value used for the model was cp = 0.007075472.

Random Forest

model_rf_reduced <- train(
  diagnosis ~ .,        # Usamos todas las variables predictoras
  data = data_reduced,
  method = "rf",        # Random Forest
  trControl = train_control,  # Control de validación cruzada
  metric = "ROC"        # Evaluar utilizando AUC (Área bajo la curva ROC)
)
note: only 2 unique complexity parameters in default grid. Truncating the grid to 2 .
# Ver el resumen del modelo entrenado
print(model_rf_reduced)
Random Forest 

569 samples
  3 predictor
  2 classes: 'B', 'M' 

No pre-processing
Resampling: Cross-Validated (5 fold) 
Summary of sample sizes: 454, 456, 455, 456, 455 
Resampling results across tuning parameters:

  mtry  ROC        Sens       Spec     
  2     0.9802611  0.9524257  0.9059801
  3     0.9809145  0.9580986  0.9059801

ROC was used to select the optimal model using the largest value.
The final value used for the model was mtry = 3.

Support Vector Machine (SVM)


model_svm_reduced <- train(
  diagnosis ~ .,        # Usamos todas las variables predictoras
  data = data_reduced,
  method = "svmRadial",  # Support Vector Machine con kernel radial
  trControl = train_control,  # Control de validación cruzada
  metric = "ROC"        # Evaluar utilizando AUC (Área bajo la curva ROC)
)

# Ver el resumen del modelo entrenado
print(model_svm_reduced)
Support Vector Machines with Radial Basis Function Kernel 

569 samples
  3 predictor
  2 classes: 'B', 'M' 

No pre-processing
Resampling: Cross-Validated (5 fold) 
Summary of sample sizes: 455, 455, 456, 455, 455 
Resampling results across tuning parameters:

  C     ROC        Sens       Spec     
  0.25  0.9838308  0.9663146  0.9148394
  0.50  0.9766560  0.9719092  0.9100775
  1.00  0.9718964  0.9663146  0.9194906

Tuning parameter 'sigma' was held constant at a value of 3.341819
ROC was used to select the optimal model using the largest value.
The final values used for the model were sigma = 3.341819 and C = 0.25.

Regresión Logística

model_logit_reduced <- train(
  diagnosis ~ .,        # Usamos todas las variables predictoras
  data = data_reduced,
  method = "glm",       # Regresión Logística
  trControl = train_control,  # Control de validación cruzada
  metric = "ROC"        # Evaluar utilizando AUC (Área bajo la curva ROC)
)
Aviso: glm.fit: fitted probabilities numerically 0 or 1 occurredAviso: glm.fit: fitted probabilities numerically 0 or 1 occurredAviso: glm.fit: fitted probabilities numerically 0 or 1 occurredAviso: glm.fit: fitted probabilities numerically 0 or 1 occurredAviso: glm.fit: fitted probabilities numerically 0 or 1 occurredAviso: glm.fit: fitted probabilities numerically 0 or 1 occurred
# Ver el resumen del modelo entrenado
print(model_logit_reduced)
Generalized Linear Model 

569 samples
  3 predictor
  2 classes: 'B', 'M' 

No pre-processing
Resampling: Cross-Validated (5 fold) 
Summary of sample sizes: 454, 456, 456, 454, 456 
Resampling results:

  ROC        Sens       Spec     
  0.9877782  0.9719484  0.9243632

Red Neuronal

# Entrenar una red neuronal para clasificación binaria
model_nn_reduced <- train(
  diagnosis ~ .,        # Usamos todas las variables predictoras
  data = data_reduced,
  method = "nnet",      # Red Neuronal
  trControl = train_control,  # Control de validación cruzada
  metric = "ROC",       # Evaluar utilizando AUC (Área bajo la curva ROC)
  verbose = FALSE       # Suprimir los detalles del entrenamiento
)
# weights:  6
initial  value 400.519235 
final  value 300.690175 
converged
# weights:  16
initial  value 356.268672 
final  value 300.690175 
converged
# weights:  26
initial  value 412.034781 
final  value 300.690175 
converged
# weights:  6
initial  value 314.005643 
iter  10 value 300.703228
iter  20 value 221.081004
iter  30 value 136.929352
iter  40 value 99.811951
iter  50 value 87.649399
final  value 87.577932 
converged
# weights:  16
initial  value 301.285795 
iter  10 value 300.686148
iter  20 value 136.607519
iter  30 value 124.817825
iter  40 value 103.150806
iter  50 value 84.234538
iter  60 value 83.465634
final  value 83.463846 
converged
# weights:  26
initial  value 301.293663 
iter  10 value 300.003317
iter  20 value 282.157619
iter  30 value 165.795734
iter  40 value 152.651474
iter  50 value 116.740898
iter  60 value 115.577534
iter  70 value 110.725993
iter  80 value 94.332311
iter  90 value 84.381715
iter 100 value 83.409259
final  value 83.409259 
stopped after 100 iterations
# weights:  6
initial  value 305.621908 
final  value 300.690241 
converged
# weights:  16
initial  value 361.486455 
final  value 300.690418 
converged
# weights:  26
initial  value 311.340217 
iter  10 value 300.690593
final  value 300.690501 
converged
# weights:  6
initial  value 360.679547 
final  value 300.168785 
converged
# weights:  16
initial  value 374.010669 
final  value 300.168785 
converged
# weights:  26
initial  value 390.782769 
final  value 300.168785 
converged
# weights:  6
initial  value 301.240052 
iter  10 value 299.822928
iter  20 value 187.499475
iter  30 value 172.149910
iter  40 value 107.382718
iter  50 value 94.617928
final  value 94.617268 
converged
# weights:  16
initial  value 319.505594 
iter  10 value 288.604390
iter  20 value 150.756314
iter  30 value 134.246082
iter  40 value 113.855061
iter  50 value 96.094488
iter  60 value 92.126186
iter  70 value 91.259665
final  value 91.245888 
converged
# weights:  26
initial  value 313.373077 
iter  10 value 299.691341
iter  20 value 217.800548
iter  30 value 151.405872
iter  40 value 134.539543
iter  50 value 100.697043
iter  60 value 96.114428
iter  70 value 92.471003
iter  80 value 90.786420
iter  90 value 90.131351
iter 100 value 89.239245
final  value 89.239245 
stopped after 100 iterations
# weights:  6
initial  value 319.084093 
final  value 300.168907 
converged
# weights:  16
initial  value 372.825090 
final  value 300.169079 
converged
# weights:  26
initial  value 309.528406 
final  value 300.169207 
converged
# weights:  6
initial  value 307.204665 
final  value 301.157329 
converged
# weights:  16
initial  value 309.616908 
final  value 301.157329 
converged
# weights:  26
initial  value 364.930574 
final  value 301.157329 
converged
# weights:  6
initial  value 301.274673 
iter  10 value 301.146206
iter  20 value 207.091691
iter  30 value 153.244440
iter  40 value 136.295694
iter  50 value 87.875298
final  value 87.746192 
converged
# weights:  16
initial  value 296.586270 
iter  10 value 164.260314
iter  20 value 133.780561
iter  30 value 115.681682
iter  40 value 86.678854
iter  50 value 86.216681
iter  60 value 86.134471
iter  70 value 86.127123
iter  80 value 86.125811
iter  90 value 86.118695
final  value 86.118684 
converged
# weights:  26
initial  value 315.152129 
iter  10 value 196.444092
iter  20 value 121.548073
iter  30 value 113.891184
iter  40 value 92.099462
iter  50 value 86.983062
iter  60 value 85.461936
iter  70 value 85.396616
iter  80 value 83.973228
iter  90 value 83.599945
iter 100 value 82.922424
final  value 82.922424 
stopped after 100 iterations
# weights:  6
initial  value 317.234355 
final  value 301.157445 
converged
# weights:  16
initial  value 395.266412 
final  value 301.157516 
converged
# weights:  26
initial  value 311.904315 
final  value 301.157595 
converged
# weights:  6
initial  value 306.388596 
final  value 300.690175 
converged
# weights:  16
initial  value 312.001573 
final  value 300.690175 
converged
# weights:  26
initial  value 400.570212 
final  value 300.690175 
converged
# weights:  6
initial  value 421.853792 
iter  10 value 300.704939
final  value 300.703858 
converged
# weights:  16
initial  value 334.824475 
iter  10 value 300.697741
final  value 300.697453 
converged
# weights:  26
initial  value 314.967856 
iter  10 value 305.265361
iter  20 value 300.816041
iter  30 value 300.695687
iter  30 value 300.695685
final  value 300.695573 
converged
# weights:  6
initial  value 308.355351 
final  value 300.690305 
converged
# weights:  16
initial  value 313.918031 
final  value 300.690419 
converged
# weights:  26
initial  value 373.835253 
final  value 300.690575 
converged
# weights:  6
initial  value 359.377126 
final  value 300.168785 
converged
# weights:  16
initial  value 302.359467 
final  value 300.168785 
converged
# weights:  26
initial  value 338.554358 
final  value 300.168785 
converged
# weights:  6
initial  value 299.237821 
iter  10 value 157.377690
iter  20 value 147.166520
iter  30 value 119.490462
iter  40 value 91.205606
final  value 91.203282 
converged
# weights:  16
initial  value 375.801693 
iter  10 value 298.830198
iter  20 value 162.443995
iter  30 value 147.097909
iter  40 value 137.969095
iter  50 value 101.924214
iter  60 value 88.555091
iter  70 value 88.420617
iter  80 value 88.310056
final  value 88.309163 
converged
# weights:  26
initial  value 431.488216 
iter  10 value 191.039484
iter  20 value 138.836570
iter  30 value 99.914254
iter  40 value 87.524878
iter  50 value 86.725075
iter  60 value 86.660641
iter  70 value 86.646334
iter  80 value 86.413626
iter  90 value 86.405290
final  value 86.405252 
converged
# weights:  6
initial  value 315.634849 
iter  10 value 149.961203
iter  20 value 148.576875
iter  30 value 133.936820
iter  40 value 76.095308
iter  50 value 57.452457
iter  60 value 57.233319
iter  70 value 57.189360
iter  80 value 57.169342
iter  90 value 57.152084
iter 100 value 57.138115
final  value 57.138115 
stopped after 100 iterations
# weights:  16
initial  value 341.277408 
final  value 300.169070 
converged
# weights:  26
initial  value 310.245251 
final  value 300.169181 
converged
# weights:  6
initial  value 482.433457 
iter  10 value 375.733808
final  value 375.733802 
converged

3.3.4. Comparación de resultados del análisis supervisado: dataset incial vs dataset reducido.

A continuación, se compararán los resultados de los modelos entrenados con el dataset completo y el dataset reducido. Se evaluarán las métricas de rendimiento, como el área bajo la curva ROC (AUC-ROC), la sensibilidad y la especificidad, para determinar si la reducción de variables afecta el rendimiento de los modelos.

# Comparativa entre los modelos
# Lista de los modelos completos y reducidos
modelos_completos <- list(cart = model_cart, rf = model_rf, svm = model_svm, logit = model_logit, nn = model_nn)
modelos_reducidos <- list(cart = model_cart_reduced, rf = model_rf_reduced, svm = model_svm_reduced, logit = model_logit_reduced, nn = model_nn_reduced)

# Inicializamos un data frame vacío para almacenar los resultados
resultados <- data.frame(
  modelo = character(),
  roc_inicial = numeric(),
  sens_inicial = numeric(),
  spec_inicial = numeric(),
  roc_reducido = numeric(),
  sens_reducido = numeric(),
  spec_reducido = numeric(),
  stringsAsFactors = FALSE
)

# Iteramos sobre los modelos completos y reducidos
for (nombre in names(modelos_completos)) {
  # Modelo completo
  modelo_completo <- modelos_completos[[nombre]]
  mejor_completo <- modelo_completo$results[which.max(modelo_completo$results$ROC), c("ROC", "Sens", "Spec")]
  
  # Modelo reducido
  modelo_reducido <- modelos_reducidos[[nombre]]
  mejor_reducido <- modelo_reducido$results[which.max(modelo_reducido$results$ROC), c("ROC", "Sens", "Spec")]
  
  # Agregar al data frame
  resultados <- rbind(resultados, data.frame(
    modelo = nombre,
    roc_inicial = mejor_completo$ROC,
    sens_inicial = mejor_completo$Sens,
    spec_inicial = mejor_completo$Spec,
    roc_reducido = mejor_reducido$ROC,
    sens_reducido = mejor_reducido$Sens,
    spec_reducido = mejor_reducido$Spec,
    stringsAsFactors = FALSE
  ))
}

# Imprimir los resultados
print(resultados)

# Una tabla mostrando el relative change de cada métrica
comparativa <- resultados %>%
  mutate(
    roc_change = (roc_reducido - roc_inicial) / roc_inicial ,
    sens_change = (sens_reducido - sens_inicial) / sens_inicial,
    spec_change = (spec_reducido - spec_inicial) / spec_inicial
  ) %>%
  select(modelo, roc_change, sens_change, spec_change)

flextable(comparativa)

modelo

roc_change

sens_change

spec_change

cart

0.002542985

-0.017678452

0.005561735

rf

-0.009931039

-0.022785315

-0.019652487

svm

-0.011056511

-0.008709263

-0.053939533

logit

0.034148336

0.029804344

-0.025338627

nn

-0.102173134

0.023513026

-0.216977809

NA

Como se puede ver en la tabla, que muestra el cambio relativo en las métricas de rendimiento de los modelos completos y reducidos, la reducción de variables no afecta significativamente el rendimiento de los modelos. En general, los cambios en el área bajo la curva ROC (AUC-ROC), la sensibilidad y la especificidad son mínimos, lo que sugiere que las variables eliminadas no tenían un impacto significativo en la capacidad predictiva de los modelos. Esto indica que el dataset reducido sigue siendo capaz de capturar los patrones relevantes en los datos y de realizar predicciones precisas sobre el diagnóstico de cáncer de mama.

Como los modelos no mejoran ni empeoran significativamente con la reducción de variables, se puede concluir que el dataset original contiene información redundante o poco relevante para la predicción del diagnóstico. Además, ahora es un proceso más eficiente y menos costoso computacionalmente, ya que se trabaja con un número menor de variables.

Las 3 variables más importantes para predecir el cáncer de mama son: “perimeter_worst”,“area_worst”,“concave.points_worst”. Ya que se consigue un resultado equivalente con un menor número de variables, los modelos son más eficientes y menos propensos al sobreajuste.

3.4. Análisis No Supervisado.

El análisis no supervisado tiene como objetivo identificar patrones ocultos o estructuras presentes en los datos sin requerir etiquetas. Para este propósito, se emplean técnicas de clustering y reducción de dimensionalidad que permiten explorar la información contenida en las características de las células. Estas técnicas facilitan la identificación de grupos de observaciones similares y proporcionan una perspectiva más clara sobre las relaciones entre las variables.

3.4.1. Preparación del Dataset

Previo a la aplicación de técnicas de análisis no supervisado, es fundamental realizar un preprocesamiento adecuado de los datos. El primer paso consiste en eliminar la columna “diagnosis”, ya que dicha variable contiene la etiqueta que se desea predecir y no se utiliza en este tipo de análisis. Posteriormente, se normalizan las características, dado que muchas técnicas, como el clustering, son sensibles a las escalas de las variables. Este proceso asegura que todas las variables tengan la misma influencia en el modelo.

data_UnSupervised <- data %>% select(-diagnosis)
head(data_UnSupervised)
data_UnSupervised$diagnosis
NULL

La normalización asegura que las variables con diferentes unidades de medida no dominen el análisis y evita que el algoritmo de clustering o reducción de dimensionalidad se vea sesgado por la magnitud de las variables. Se utiliza la función preProcess para calcular los parámetros de normalización y con la función predict, se aplican los parámetros calculados previamente al conjunto de datos, generando una nueva versión normalizada de las características

# Normalizar los datos 
preprocess_params <- preProcess(data_UnSupervised[, -ncol(data_UnSupervised)], method = c("center", "scale"))
data_normalized <- predict(preprocess_params, data_UnSupervised[, -ncol(data_UnSupervised)])

El primer método de análisis no supervisado que se emplea es el clustering, el cual tiene como propósito agrupar las observaciones en función de sus similitudes.

3.4.2. Clustering: Determinación del Número Óptimo de Clusters

En el análisis de clustering, es fundamental determinar el número óptimo de grupos o clusters, ya que este parámetro impacta directamente en la calidad de la segmentación. Existen diversas metodologías para identificar este valor, las cuales se basan en criterios estadísticos y geométricos que evalúan la estructura de los datos y la cohesión de los clusters formados.

El primer paso para aplicar técnicas de clustering consiste en identificar el número adecuado de clusters. Dos métodos comunes para este propósito son:

Método del codo: Este enfoque evalúa la variación explicada en función del número de clusters. Se identifica el “codo” en la gráfica, que corresponde al punto donde la mejora en la varianza explicada se estabiliza, indicando que añadir más clusters no produce beneficios significativos.

Índice de Silhouette: Este índice mide la similitud de cada punto con su propio cluster en comparación con otros clusters. Valores cercanos a +1 sugieren que los puntos están correctamente agrupados, mientras que valores cercanos a -1 indican posibles errores en la asignación de clusters.

# Método del codo
wss <- (nrow(data_normalized) - 1) * sum(apply(data_normalized, 2, var))
for (i in 2:15) wss[i] <- sum(kmeans(data_normalized, centers = i)$withinss)

plot(1:15, wss, type = "b", xlab = "Número de Clusters", ylab = "Suma de Cuadrados Internos")


# Índice silhouette

silhouette_scores <- numeric()
for (i in 2:15) {
  km <- kmeans(data_normalized, centers = i)
  silhouette_scores[i] <- mean(silhouette(km$cluster, dist(data_normalized))[, 3])
}
plot(2:15, silhouette_scores[-1], type = "b", xlab = "Número de Clusters", ylab = "Puntaje de Silhouette")

Puede verse de forma más clara en el puntaje de Silhouette que k será 2.

El siguiente método de análisis no supervisado que se utiliza es el algoritmo K-means, el cual permite agrupar los datos en un número específico de clusters previamente determinado.

3.4.3. K-Means

El algoritmo K-means es una técnica de clustering que organiza los datos en K clusters, con el objetivo de minimizar la varianza dentro de cada grupo.

set.seed(123)

optimal_clusters = 2

kmeans_model <- kmeans(data_normalized, centers = optimal_clusters, nstart = 25)

# Agregar etiquetas de cluster al dataset original
data$cluster <- kmeans_model$cluster

# Mostrar los clusters de diferentes formas:

# Tabla de frecuencias
table(data$cluster)

  1   2 
385 184 
# Resumen de los datos agrupados por cluster
aggregate(data[, -ncol(data)], by = list(cluster = data$cluster), FUN = mean)
Aviso: argument is not numeric or logical: returning NAAviso: argument is not numeric or logical: returning NA
table(data$diagnosis, data$cluster)
   
      1   2
  B 348   9
  M  37 175
 1   2

B 348 9 M 37 175 Podemos observar que se han creado dos clusters, con 385 y 184 observaciones respectivamente. Además, se puede ver claramente que el cluster 1 contiene principalmente observaciones de la clase “B” (benigno), mientras que el cluster 2 contiene principalmente observaciones de la clase “M” (maligno). Esto sugiere que el algoritmo K-means ha sido capaz de agrupar las observaciones en función de su similitud, lo que facilita la identificación de patrones en los datos.

3.4.4. Clustering jerárquico

El clustering jerárquico es una técnica que organiza los datos en una estructura de árbol, donde los clusters se forman de manera recursiva mediante la unión o división de grupos. Este enfoque permite visualizar la estructura de los datos en diferentes niveles de granularidad y facilita la interpretación de las relaciones entre las observaciones.

# Calcular distancias y realizar clustering jerárquico
hierarchical_model <- hclust(dist(data_normalized), method = "ward.D2")

# Visualizar el dendrograma
plot(hierarchical_model, main = "Dendrograma", sub = "", xlab = "", cex = 0.6)


# Cortar el dendrograma en el número óptimo de clusters
optimal_clusters <- 2
data$cluster <- cutree(hierarchical_model, k = optimal_clusters)

# Mostrar los clusters de diferentes formas:

# Tabla de frecuencias
table(data$cluster)

  1   2 
233 336 
# Resumen de los datos agrupados por cluster
aggregate(data[, -ncol(data)], by = list(cluster = data$cluster), FUN = mean)
Aviso: argument is not numeric or logical: returning NAAviso: argument is not numeric or logical: returning NA
NA

El dendrograma muestra la estructura jerárquica de los datos, donde las observaciones se agrupan en clusters en función de su similitud. Al cortar el dendrograma en el número óptimo de clusters, se obtiene una partición de los datos en dos grupos, lo que permite identificar las relaciones entre las observaciones y visualizar la estructura subyacente de los datos.

3.4.5. DBSCAN

El algoritmo DBSCAN (Density-Based Spatial Clustering of Applications with Noise) es una técnica de clustering que agrupa los datos en función de la densidad de los puntos. Este enfoque es útil para identificar clusters de formas arbitrarias y detectar outliers en los datos.

En este caso, se ajusta el modelo DBSCAN con un radio máximo de 10 y un mínimo de 2 puntos para formar un cluster. Los puntos que no pertenecen a ningún cluster se consideran ruido.



# Ajustar el modelo DBSCAN
eps <- 10  # Radio máximo para vecinos
minPts <- 2  # Puntos mínimos para formar un cluster
dbscan_model <- dbscan(data_normalized, eps = eps, minPts = minPts)

# Agregar etiquetas de cluster al dataset original
data$cluster <- dbscan_model$cluster

# Mostrar los clusters de diferentes formas:

# Tabla de frecuencias (los valores 0 son ruido)
table(data$cluster)

  0   1   2 
  1 566   2 
# Resumen de los datos agrupados por cluster (excluyendo ruido)
aggregate(data[data$cluster > 0, -ncol(data)], 
          by = list(cluster = data$cluster[data$cluster > 0]), 
          FUN = mean)
Aviso: argument is not numeric or logical: returning NAAviso: argument is not numeric or logical: returning NA

Como se puede observar, el algoritmo DBSCAN identifica 2 clusters y asigna los puntos que no pertenecen a ningún cluster como ruido. Uno de ellos con 566 por lo que no ha realizado una buena agrupación.


# Calcular el coeficiente de silueta para diferentes métodos
silhouette_kmeans <- silhouette(kmeans_model$cluster, dist(data_normalized))
silhouette_hierarchical <- silhouette(cutree(hierarchical_model, k = optimal_clusters), dist(data_normalized))
silhouette_dbscan <- silhouette(dbscan_model$cluster, dist(data_normalized))

# Visualizar los coeficientes
plot(silhouette_kmeans, main = "Silueta - K-Means")

plot(silhouette_hierarchical, main = "Silueta - Clustering Jerárquico")

plot(silhouette_dbscan, main = "Silueta - DBSCAN")

Como resultado de la comparación, el k-means tiene una media de anchura de silueta de 0,35, el clustering jerárquico de 0,29 y el DBSCAN de 0,35. Esto sugiere que los clusters generados por k-means y DBSCAN son más cohesivos y bien definidos en comparación con el clustering jerárquico. El DBSCAN y k-means tienen una mayor cohesión y separación entre los clusters, lo que indica que estos algoritmos son más efectivos para agrupar los datos en función de la similitud entre las observaciones.

El siguiente paso en nuestro análisis no supervisado es aplicar el Análisis de Componentes Principales (PCA). Esta técnica permitirá visualizar la estructura de los datos, así como validar los resultados obtenidos a través del clustering.

3.4.4. Reducción de dimensionalidad con PCA

La dimensionalidad se reducirá transformando las variables originales en componentes principales para identificar la mayor parte de la variabilidad en los datos. Esto facilita la visualización de la estructura subyacente y optimiza la interpretación de los resultados del clustering. PCA simplifica el análisis al reducir la complejidad de los datos. Seleccionamos los componentes principales que expliquen al menos el 95% de la varianza acumulada.

pca_model <- prcomp(data_normalized, scale = TRUE)
explained_variance <- cumsum(pca_model$sdev^2 / sum(pca_model$sdev^2))
num_components <- which(explained_variance >= 0.95)[1]  # Seleccionar componentes que expliquen el 95% de la varianza
data_pca <- as.data.frame(pca_model$x[, 1:num_components])

3.4.5. Reducción de dimensionalidad con t-SNE

t-distributed Stochastic Neighbor Embedding es una técnica no supervisada de reducción no lineal de la dimensionalidad para la exploración de datos y la visualización de datos de alta dimensión:


set.seed(42)
tsne_model <- Rtsne(data_normalized, perplexity = 30, theta = 0.5, dims = 2)
data_tsne <- as.data.frame(tsne_model$Y)
colnames(data_tsne) <- c("Dim1", "Dim2")

3.4.6. Clustering en los Datasets Reducidos

Aplicamos K-means a los datasets generados por PCA y t-SNE, utilizando el mismo número de clusters determinado previamente.

3.4.6.1 K-means con PCA

kmeans_pca <- kmeans(data_pca, centers = optimal_clusters, nstart = 25)
data_pca$cluster <- kmeans_pca$cluster

# Visualización
ggplot(data_pca, aes(x = PC1, y = PC2, color = as.factor(cluster))) +
  geom_point() +
  theme_minimal() +
  labs(title = "Clustering K-means (PCA)")

3.4.6.2 K-means con t-SNE

kmeans_tsne <- kmeans(data_tsne, centers = optimal_clusters, nstart = 25)
data_tsne$cluster <- kmeans_tsne$cluster

# Visualización
ggplot(data_tsne, aes(x = Dim1, y = Dim2, color = as.factor(cluster))) +
  geom_point() +
  theme_minimal() +
  labs(title = "Clustering K-means (t-SNE)")

3.4.6.3 Comparación de Resultados

# Visualización PCA
ggplot(data_pca, aes(x = PC1, y = PC2, color = as.factor(cluster))) +
  geom_point() +
  theme_minimal() +
  labs(title = "Clusters visualizados en espacio PCA")


# Visualización t-SNE
ggplot(data_tsne, aes(x = Dim1, y = Dim2, color = as.factor(cluster))) +
  geom_point() +
  theme_minimal() +
  labs(title = "Clusters visualizados en espacio t-SNE")

Se visualizan claramente dos clústeres.

Los clusters están bien definidos y separados espacialmente.

Existe un grado menor de solapamiento entre los clusters, lo que sugiere que las variables originales ofrecen una separación más clara entre las clases en este espacio.

3.4.6.4 Índice Silouhette

Para visualizar mejor la comparación de ello vamos a usar el Silhouette Score, una métrica utilzada para evaluar la calidad de los clusters. El valor oscila entre -1 y 1, donde:

  • Un valor cercano a 1 indica que los puntos están bien agrupados.
  • Un valor cercano a 0 indica que los puntos están en la frontera, es decir, el agrupamiento resulta ambiguo.
  • Un valor cercano a -1 sugiere que los puntos podrían haber sido asignados al cluster incorrecto, es decir, el modelo no logró agruparlos adecuadamente.
# Silhouette para clustering con PCA
silhouette_pca <- mean(silhouette(kmeans_pca$cluster, dist(data_pca))[, 3])

# Silhouette para clustering con t-SNE
silhouette_tsne <- mean(silhouette(kmeans_tsne$cluster, dist(data_tsne))[, 3])

# Comparación
cat("Silhouette Score - Sin reducción:", silhouette_scores[optimal_clusters], "\n")
Silhouette Score - Sin reducción: 0.3482031 
cat("Silhouette Score - PCA:", silhouette_pca, "\n")
Silhouette Score - PCA: 0.3678096 
cat("Silhouette Score - t-SNE:", silhouette_tsne, "\n")
Silhouette Score - t-SNE: 0.5216206 

Silhouette Score - Sin reducción: 0.3482031 Silhouette Score - PCA: 0.3678096 Silhouette Score - t-SNE: 0.5216206 El Silhouette Score sin reducción es 0.34, lo que indica una media calidad de los clusters. Un valor cercano a 0.7 es generalmente considerado como un indicador de que el modelo está logrando una buena separación entre los clusters y que los puntos dentro de cada cluster son bastante similares entre sí. El Silhouette Score después de realizar PCA es 0.36, lo que indica una mejora en la calidad de los clusters en comparación con los datos originales. Esto sugiere que la reducción de dimensionalidad ha permitido una mejor separación de los clusters y una mayor cohesión dentro de cada cluster. El Silhouette Score después de realizar t-SNE es 0.52, lo que indica una mejora significativa en la calidad de los clusters en comparación con los datos originales y con la reducción de dimensionalidad mediante PCA. Esto sugiere que t-SNE ha logrado una mejor separación de los clusters y una mayor cohesión dentro de cada cluster.

El siguiente algoritmo utilizado en el análisis no supervisado es el Apriori para reglas de asociación.

3.4.6. Reglas de asociación con algoritmo Apriori.

Las reglas de asociación se emplean en Data Mining para identificar relaciones entre elementos de un conjunto de datos. Estas relaciones ayudan a encontrar patrones en los datos y están formadas por un antecedente {a} y un consecuente {b}, indicando que {a} -> {b}. Es relevante conocer términos como:

Soporte: Proporción de transacciones que contienen ambos conjuntos de ítems (antecedente y consecuente). Confianza: Indicador de cuán bien predice el antecedente al consecuente.

Algoritmo Apriori

Se utilizará el Algoritmo Apriori para obtener las reglas de asociación, ya que resulta útil para identificar itemsets frecuentes en datos transaccionales, como eventos registrados en un intervalo de tiempo determinado.

Fase 1: Reducción del número de candidatos
  1. Primero, se generan todos los itemsets con un único ítem. Luego, estos itemsets se combinan para formar itemsets con dos elementos, y así sucesivamente. Se seleccionarán únicamente los pares cuyo soporte sea mayor o igual a un umbral minsup, eliminando aquellos que no cumplan con este criterio.

Se instalará el paquete necesario (arules), que es un paquete en R que proporciona funcionalidades para trabajar con reglas de asociación.

  1. Se cargan los datos como transacciones en el formato basket, interpretando cada transacción como un conjunto de ítems (basket). Esto permite manejar tanto datos categóricos (como diagnosis) como continuos (los otros atributos) en la misma transacción.
transacciones <- read.transactions("data_reduced.csv", format = "basket", sep = ",")
Fase 2: Generar reglas Parámetros: soporte mínimo (0.01 -> 1%) y confianza mínima (80%)

En esta fase, se procederá a generar las reglas de asociación utilizando los parámetros de soporte mínimo (0.01 - 1%) y confianza mínima (80%). Estos valores permiten filtrar las reglas que son suficientemente frecuentes y confiables, asegurando que se obtengan relaciones significativas entre los ítems en los datos.

reglasAsociacion <- apriori(transacciones, parameter = list(supp = 0.01, conf = 0.8))
Apriori

Parameter specification:

Algorithmic control:

Absolute minimum support count: 5 

set item appearances ...[0 item(s)] done [0.00s].
set transactions ...[1556 item(s), 570 transaction(s)] done [0.00s].
sorting and recoding items ... [3 item(s)] done [0.00s].
creating transaction tree ... done [0.00s].
checking subsets of size 1 2 done [0.00s].
writing ... [1 rule(s)] done [0.00s].
creating S4 object  ... done [0.00s].

Se filtran las reglas basadas en el lift y la confianza, de modo que solo se consideren aquellas con un lift mayor a 1.5 y una confianza superior al 80%. Un lift > 1.5 indica una fuerte asociación entre los ítems en la regla, mientras que una confianza ≥ 0.8 asegura que la probabilidad de que el consecuente ocurra cuando el antecedente está presente es alta.

filtradas <- subset(reglasAsociacion, lift > 1.5 & confidence >= 0.8)
inspect(filtradas)

#imprimir lhs como variable original
inspect(filtradas, data = TRUE)
NA

Como se puede observar, se ha generado una regla de asociación con un soporte del 2.28%, una confianza del 100% y un lift de 1.59.

No se ha conseguido encontrar reglas de asociación significativas con los datos reducidos. Esto puede deberse a la falta de variabilidad en los datos o a la reducción de variables, lo que limita la capacidad del algoritmo para encontrar relaciones significativas entre los ítems.

3.4.6.1 Visualización de las Reglas de Asociación:

# Instalar el paquete de visualización


# Visualizar las reglas
plot(filtradas, method = "graph")

4. Conclusiones

Hemos llevado a cabo un análisis exhaustivo del conjunto de datos de cáncer de mama, explorando tanto modelos de aprendizaje supervisado como no supervisado y la identificación de reglas de asociación. Los modelos Random Forest y SVM destacaron por su rendimiento excepcional, logrando altas puntuaciones de AUC y sensibilidad. Las variables más importantes para predecir el cáncer de mama fueron “perimeter_worst”, “area_worst” y “concave.points_worst”. La eliminación de variables menos importantes no afectó significativamente el desempeño de los modelos, lo que sugiere que estas variables no aportaban información relevante ni introducían ruido, permitiendo modelos más eficientes y menos propensos al sobreajuste. En el aprendizaje no supervisado, el clustering sin reducción de dimensionalidad proporcionó la mejor separación entre clusters, con un Silhouette Score más alto de 0.68. Sin embargo, la PCA redujo significativamente la calidad del clustering al eliminar componentes clave. t-SNE mostró una calidad intermedia (0.54) en comparación con el espacio original de las variables, destacando su utilidad para la visualización pero limitando la preservación de la estructura del clustering. Este análisis subraya la importancia de emplear diversas técnicas de análisis de datos para obtener una comprensión completa de los patrones y relaciones en los datos de cáncer de mama.

5. Referencias

LS0tDQp0aXRsZTogIlIgTm90ZWJvb2siDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQNCiAgd29yZF9kb2N1bWVudDogZGVmYXVsdA0KICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBkZl9wcmludDogcGFnZWQNCi0tLQ0KDQotLS0NCnRpdGxlOiAiRklELUc1Ig0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KIyBBbsOhbGlzaXMgZGUgRGF0b3MgZGUgSW3DoWdlbmVzIEZOQSBwYXJhIGVsIERpYWduw7NzdGljbyBkZSBDw6FuY2VyIGRlIE1hbWENCg0KKipBdXRvcmVzOioqIENsYXVkaWEgSGVyZWRpYSBDZWJhbGxvcywgTWFudWVsIE90ZXJvIEJhcmJhc8OhbiwgTWFydGEgUGluZWRhIEdpc2JlcnQsIEphdmllciBGZXJuw6FuZGV6IENhc3RpbGxvLg0KDQoqKkdydXBvOioqIEc1Lg0KDQoqKk9yZ2FuaXphY2nDs24qKjogVW5pdmVyc2lkYWQgZGUgc2V2aWxsYS4NCg0KIyMgUmVzdW1lbg0KDQogIEVzdGUgcHJveWVjdG8gdGllbmUgY29tbyBvYmpldGl2byBsYSBhcGxpY2FjacOzbiBkZSB0w6ljbmljYXMgZGUgYXByZW5kaXphamUgc3VwZXJ2aXNhZG8geSBubyBzdXBlcnZpc2FkbyBwYXJhIGVsIGRpYWduw7NzdGljbyBkZSBjw6FuY2VyIGRlIG1hbWEsIHV0aWxpemFuZG8gdW4gY29uanVudG8gZGUgZGF0b3MgcXVlIGNvbnRpZW5lIGNhcmFjdGVyw61zdGljYXMgZXh0cmHDrWRhcyBkZSBpbcOhZ2VuZXMgZGUgYXNwaXJhZG8gY29uIGFndWphIGZpbmEgKEZOQSkgZGUgbWFzYXMgbWFtYXJpYXMuIEVsIGNvbmp1bnRvIGRlIGRhdG9zIGNvbnRpZW5lIDU2OSBtdWVzdHJhcywgY29uIDM1NyBiZW5pZ25hcyB5IDIxMiBtYWxpZ25hcywgeSAzMyBjYXJhY3RlcsOtc3RpY2FzIHF1ZSBkZXNjcmliZW4gZGl2ZXJzYXMgcHJvcGllZGFkZXMgZGUgbG9zIG7DumNsZW9zIGNlbHVsYXJlcyBlbiBsYXMgaW3DoWdlbmVzLiBFbiBlc3RlIGVzdHVkaW8sIHNlIGV4cGxvcmFyw6FuIHZhcmlvcyBlbmZvcXVlcyBkZSBjbGFzaWZpY2FjacOzbiBzdXBlcnZpc2FkYSwgY29tbyAqKkNBUlQqKiwgKipSYW5kb20gRm9yZXN0KiosICoqU1ZNKiosICoqUmVncmVzacOzbiBsb2fDrXN0aWNhKiogeSAqKnJlZGVzIG5ldXJvbmFsZXMqKiBwYXJhIHByZWRlY2lyIGVsIGRpYWduw7NzdGljbyBkZSBsYXMgbXVlc3RyYXMsIGFkZW3DoXMgZGUgYXBsaWNhciB0w6ljbmljYXMgbm8gc3VwZXJ2aXNhZGFzIGNvbW8gZWwgKipjbHVzdGVyaW5nKiogeSBsYSAqKnJlZHVjY2nDs24gZGUgZGltZW5zaW9uYWxpZGFkKiogYSB0cmF2w6lzIGRlICoqUENBKiogeSAqKlQtU05FKiouIFNlIGVzcGVyYSBxdWUgZWwgYW7DoWxpc2lzIGNvbWJpbmFkbyBkZSBhbWJvcyBlbmZvcXVlcyBwZXJtaXRhIG1lam9yYXIgbGEgcHJlY2lzacOzbiBlbiBlbCBkaWFnbsOzc3RpY28geSBwcm9wb3JjaW9uYXIgdW5hIG1lam9yIGNvbXByZW5zacOzbiBkZSBsb3MgcGF0cm9uZXMgc3VieWFjZW50ZXMgZW4gbG9zIGRhdG9zLiAqKlNlIGRpc2N1dGlyw6FuIGxvcyByZXN1bHRhZG9zIG9idGVuaWRvcyB5IHNlIHByZXNlbnRhcsOhbiBjb25jbHVzaW9uZXMgc29icmUgbGEgZWZlY3RpdmlkYWQgZGUgbGFzIHTDqWNuaWNhcyBlbXBsZWFkYXMuKioNCg0KIyMgMS4gSW50cm9kdWNjacOzbg0KDQpFbCBkaWFnbsOzc3RpY28gdGVtcHJhbm8geSBwcmVjaXNvIGRlbCBjw6FuY2VyIGRlIG1hbWEgZXMgY3J1Y2lhbCBwYXJhIHVuIHRyYXRhbWllbnRvIGV4aXRvc28uIEVuIGVzdGUgZXN0dWRpbywgc2UgZW1wbGVhIGVsIGNvbmp1bnRvIGRlIGRhdG9zIFsqKkJyZWFzdCBDYW5jZXIgV2lzY29uc2luIChEaWFnbm9zdGljKSBEYXRhIFNldCoqXShodHRwczovL2FyY2hpdmUuaWNzLnVjaS5lZHUvbWwvZGF0YXNldHMvQnJlYXN0K0NhbmNlcitXaXNjb25zaW4rJTI4RGlhZ25vc3RpYyUyOSksIHF1ZSBjb250aWVuZSBjYXJhY3RlcsOtc3RpY2FzIGRlcml2YWRhcyBkZSBpbcOhZ2VuZXMgRk5BIGRlIGPDqWx1bGFzIGRlIHRlamlkbyBtYW1hcmlvLg0KDQpFbCBGTkEgZXMgdW5hIHTDqWNuaWNhIGRlIGRpYWduw7NzdGljbyB1dGlsaXphZGEgcGFyYSBldmFsdWFyIGxhIG5hdHVyYWxlemEgZGUgbGFzIG1hc2FzIG1hbWFyaWFzLiBEdXJhbnRlIGVsIHByb2NlZGltaWVudG8sIHNlIGV4dHJhZSB1bmEgcGVxdWXDsWEgY2FudGlkYWQgZGUgdGVqaWRvIGRlIGxhIG1hc2EgeSBzZSBleGFtaW5hIGJham8gdW4gbWljcm9zY29waW8gcGFyYSBkZXRlcm1pbmFyIHNpIGVzIGJlbmlnbmEgbyBtYWxpZ25hLiBMYXMgY2FyYWN0ZXLDrXN0aWNhcyBkZSBsYXMgY8OpbHVsYXMsIGNvbW8gbGEgZm9ybWEsIGVsIHRhbWHDsW8geSBsYSB0ZXh0dXJhIGRlIGxvcyBuw7pjbGVvcywgcHVlZGVuIHByb3BvcmNpb25hciBpbmZvcm1hY2nDs24gdmFsaW9zYSBzb2JyZSBsYSBuYXR1cmFsZXphIGRlbCB0dW1vci4NCg0KRW4gbGEgKipGaWd1cmEgMSoqIHNlIGlsdXN0cmEgZWwgcHJvY2VkaW1pZW50byBkZSBhc3BpcmFjacOzbiBjb24gYWd1amEgZmluYSAoRk5BKSwgcXVlIG11ZXN0cmEgbGEgaW5zZXJjacOzbiBkZSBsYSBhZ3VqYSBwYXJhIGV4dHJhZXIgbGFzIGPDqWx1bGFzIGRlbCB0dW1vci4gTGEgKipGaWd1cmEgMioqIHByZXNlbnRhIHVuYSBpbWFnZW4gbWljcm9zY8OzcGljYSBkZSBsYXMgY8OpbHVsYXMgZXh0cmHDrWRhcywgcGVybWl0aWVuZG8gb2JzZXJ2YXIgc3VzIGNhcmFjdGVyw61zdGljYXMgdW5hIHZleiByZWFsaXphZG8gZWwgRk5BLg0KDQo6Ojoge3N0eWxlPSJ0ZXh0LWFsaWduOiBjZW50ZXI7In0NCjxpbWcgc3JjPSJpbWFnZXMvZmluZS1uZWVkbGUtYXNwaXJhdGlvbi11c2luZy11bHRyYXNvdW5kLmpwZyIgYWx0PSJQcm9jZWRpbWllbnRvIGRlIEFzcGlyYWNpw7NuIGNvbiBBZ3VqYSBGaW5hIChGTkEpIiBzdHlsZT0id2lkdGg6IDM1JTsiLz4gPGJyPiA8Yj5GaWd1cmEgMTo8L2I+PGk+UHJvY2VkaW1pZW50byBGTkE8L2k+DQo6OjoNCg0KOjo6IHtzdHlsZT0idGV4dC1hbGlnbjogY2VudGVyOyJ9DQo8aW1nIHNyYz0iaW1hZ2VzL2NlbGxzLmpwZyIgYWx0PSJDw6lsdWxhcyBleHRyYcOtZGFzIGRlIEZOQSIgc3R5bGU9IndpZHRoOiAzNSU7Ii8+IDxicj4gPGI+RmlndXJhIDI6PC9iPjxpPiBDw6lsdWxhcyBleHRyYcOtZGFzIGRlIEZOQTwvaT4NCjo6Og0KDQpFbCBjb25qdW50byBkZSBkYXRvcyBpbmNsdXllIDMzIGNhcmFjdGVyw61zdGljYXMsIGNvbW8gZWwgcmFkaW8sIGxhIHRleHR1cmEsIGVsIHBlcsOtbWV0cm8geSBsYSBzaW1ldHLDrWEgZGUgbG9zIG7DumNsZW9zIGNlbHVsYXJlcywgbGFzIGN1YWxlcyBzZSB1dGlsaXphbiBwYXJhIGRldGVybWluYXIgc2kgdW5hIG11ZXN0cmEgZXMgKipiZW5pZ25hKiogbyAqKm1hbGlnbmEqKi4gIA0KDQpFc3RhcyBjYXJhY3RlcsOtc3RpY2FzIHNlIHB1ZWRlbiBhZ3J1cGFyIGVuOiAgDQoNCioqUmVsYWNpb25hZGFzIGNvbiBlbCB0YW1hw7FvKiogIA0KSW5jbHV5ZW4gbWVkaWRhcyBxdWUgZGVzY3JpYmVuIGVsIHRhbWHDsW8geSBsYSBleHRlbnNpw7NuIGRlIGxvcyBuw7pjbGVvcyBjZWx1bGFyZXMuIEVsICoqcmFkaW8qKiBtaWRlIGVsIHRhbWHDsW8gZGUgbGEgY2lyY3VuZmVyZW5jaWEgeSBzZSByZXBvcnRhIGNvbW8gbGEgbWVkaWEgKGByYWRpdXNfbWVhbmApLCBlbCBwZW9yIGNhc28gKGByYWRpdXNfd29yc3RgKSB5IGxhIGRlc3ZpYWNpw7NuIGVzdMOhbmRhciAoYHJhZGl1c19zZWApLiBFbCAqKnBlcsOtbWV0cm8qKiBpbmRpY2EgbGEgbG9uZ2l0dWQgYWxyZWRlZG9yIGRlbCBuw7pjbGVvLCByZXBvcnRhZG8gZGUgbWFuZXJhIHNpbWlsYXIgY29tbyBtZWRpYSAoYHBlcmltZXRlcl9tZWFuYCksIHBlb3IgY2FzbyAoYHBlcmltZXRlcl93b3JzdGApIHkgZGVzdmlhY2nDs24gZXN0w6FuZGFyIChgcGVyaW1ldGVyX3NlYCkuIFBvciDDumx0aW1vLCBlbCAqKsOhcmVhKiogcmVwcmVzZW50YSBlbCBlc3BhY2lvIG9jdXBhZG8gcG9yIGVsIG7DumNsZW8sIHRhbWJpw6luIGRpdmlkaWRvIGVuIG1lZGlhIChgYXJlYV9tZWFuYCksIHBlb3IgY2FzbyAoYGFyZWFfd29yc3RgKSB5IGRlc3ZpYWNpw7NuIGVzdMOhbmRhciAoYGFyZWFfc2VgKS4gIA0KDQoqKlJlbGFjaW9uYWRhcyBjb24gbGEgZm9ybWEqKiAgDQpFc3RhcyB2YXJpYWJsZXMgZGVzY3JpYmVuIGNhcmFjdGVyw61zdGljYXMgZ2VvbcOpdHJpY2FzIGRlIGxvcyBuw7pjbGVvcywgZW5mb2PDoW5kb3NlIGVuIGlycmVndWxhcmlkYWRlcyBlbiBzdSBib3JkZS4gTGEgKipzdWF2aWRhZCoqIG1pZGUgbGEgcmVndWxhcmlkYWQgZGUgbG9zIGJvcmRlcyAoYHNtb290aG5lc3NfbWVhbmAsIGBzbW9vdGhuZXNzX3dvcnN0YCwgYHNtb290aG5lc3Nfc2VgKSwgbWllbnRyYXMgcXVlIGxhICoqY29tcGFjaWRhZCoqIGV2YWzDumEgcXXDqSB0YW4gcmVkb25kYSBlcyBsYSBmb3JtYSBkZWwgbsO6Y2xlbyAoYGNvbXBhY3RuZXNzX21lYW5gLCBgY29tcGFjdG5lc3Nfd29yc3RgLCBgY29tcGFjdG5lc3Nfc2VgKS4gTGEgKipjb25jYXZpZGFkKiogbWlkZSBlbCBncmFkbyBkZSBodW5kaW1pZW50byBlbiBsb3MgYm9yZGVzIChgY29uY2F2aXR5X21lYW5gLCBgY29uY2F2aXR5X3dvcnN0YCwgYGNvbmNhdml0eV9zZWApIHkgbG9zICoqcHVudG9zIGPDs25jYXZvcyoqIHJlcHJlc2VudGFuIGVsIG7Dum1lcm8gZGUgcHVudG9zIGVzcGVjw61maWNvcyBkb25kZSBlc3RvcyBodW5kaW1pZW50b3Mgc29uIGV2aWRlbnRlcyAoYGNvbmNhdmUgcG9pbnRzX21lYW5gLCBgY29uY2F2ZSBwb2ludHNfd29yc3RgLCBgY29uY2F2ZSBwb2ludHNfc2VgKS4gIA0KDQoqKlJlbGFjaW9uYWRhcyBjb24gbGEgdGV4dHVyYSB5IGZyYWN0YWxlcyoqICANCkVzdGFzIGNhcmFjdGVyw61zdGljYXMgZXN0w6FuIGFzb2NpYWRhcyBhIGxhIGFwYXJpZW5jaWEgeSBjb21wbGVqaWRhZCBkZSBsYSBpbWFnZW4gZGUgbG9zIG7DumNsZW9zIGNlbHVsYXJlcy4gTGEgKip0ZXh0dXJhKiogbWlkZSBsYSB2YXJpYWNpw7NuIGVuIGxhIGludGVuc2lkYWQgZGUgbG9zIHDDrXhlbGVzIHkgc2UgcmVwb3J0YSBjb21vIG1lZGlhIChgdGV4dHVyZV9tZWFuYCksIHBlb3IgY2FzbyAoYHRleHR1cmVfd29yc3RgKSB5IGRlc3ZpYWNpw7NuIGVzdMOhbmRhciAoYHRleHR1cmVfc2VgKS4gUG9yIHN1IHBhcnRlLCBsYSAqKmRpbWVuc2nDs24gZnJhY3RhbCoqIGRlc2NyaWJlIGxhIGlycmVndWxhcmlkYWQgZGVsIGJvcmRlIGRlIGxvcyBuw7pjbGVvcyBjZWx1bGFyZXMgeSB0YW1iacOpbiBzZSBtaWRlIGNvbW8gbWVkaWEgKGBmcmFjdGFsX2RpbWVuc2lvbl9tZWFuYCksIHBlb3IgY2FzbyAoYGZyYWN0YWxfZGltZW5zaW9uX3dvcnN0YCkgeSBkZXN2aWFjacOzbiBlc3TDoW5kYXIgKGBmcmFjdGFsX2RpbWVuc2lvbl9zZWApLiAgDQoNCg0KRWwgYW7DoWxpc2lzIHNlIGNlbnRyYSBlbiBsYSBhcGxpY2FjacOzbiBkZSB0w6ljbmljYXMgZGUgYXByZW5kaXphamUgc3VwZXJ2aXNhZG8sIGNvbW8gKipDQVJUKiosICoqUmFuZG9tIEZvcmVzdCoqIHkgKipTVk0qKiwgcGFyYSBwcmVkZWNpciBlbCBkaWFnbsOzc3RpY28gYmFzYWRvIGVuIGxhcyBjYXJhY3RlcsOtc3RpY2FzIG51bcOpcmljYXMuIEFkZW3DoXMsIHNlIGFwbGljYXLDoW4gdMOpY25pY2FzIGRlIGFwcmVuZGl6YWplIG5vIHN1cGVydmlzYWRvLCBjb21vIGVsICoqY2x1c3RlcmluZyoqIHkgbGEgKipyZWR1Y2Npw7NuIGRlIGRpbWVuc2lvbmFsaWRhZCoqIG1lZGlhbnRlICoqUENBKiogLCBwYXJhIGV4cGxvcmFyIGxvcyBwYXRyb25lcyBvY3VsdG9zIGVuIGxvcyBkYXRvcyB5IHBvc2libGVtZW50ZSBtZWpvcmFyIGxhIHByZWNpc2nDs24gZGUgbG9zIG1vZGVsb3MuDQoNCiMjIDIuIE1ldG9kb2xvZ8OtYQ0KDQpFbCBkZXNhcnJvbGxvIGRlIGVzdGUgcHJveWVjdG8gc2UgbGxldmFyw6EgYSBjYWJvIGEgdHJhdsOpcyBkZSB1biBlbmZvcXVlIGVzdHJ1Y3R1cmFkbyB5IHNlY3VlbmNpYWwgZW4gdmFyaWFzIGZhc2VzLCBjb24gZWwgb2JqZXRpdm8gZGUgYW5hbGl6YXIgeSBwcmVkZWNpciBlbCBkaWFnbsOzc3RpY28gZGUgY8OhbmNlciBkZSBtYW1hIHV0aWxpemFuZG8gaW3DoWdlbmVzIEZOQS4gTG9zIHBhc29zIGNsYXZlIGRlbCBwcm9jZXNvIHNvbiBsb3Mgc2lndWllbnRlczoNCg0KMS4gICoqQW7DoWxpc2lzIEluaWNpYWwgeSBQcmVwcm9jZXNhbWllbnRvIGRlIGxvcyBEYXRvcyoqXA0KICAgIEVuIGVzdGEgZmFzZSBpbmljaWFsLCBzZSBwcm9jZWRlcsOhIGNvbiBsYSBjYXJnYSBkZSBsb3MgZGF0b3MgZGVsIGNvbmp1bnRvIGRlIGRhdG9zICJCcmVhc3QgQ2FuY2VyIFdpc2NvbnNpbiAoRGlhZ25vc3RpYykiLCBxdWUgY29udGllbmUgbGFzIGNhcmFjdGVyw61zdGljYXMgZXh0cmHDrWRhcyBkZSBpbcOhZ2VuZXMgZGUgYXNwaXJhY2nDs24gY29uIGFndWphIGZpbmEgKEZOQSkuIFNlIHJlYWxpemFyw6EgdW5hIGV4cGxvcmFjacOzbiBwcmVsaW1pbmFyIGRlIGxvcyBkYXRvcyBwYXJhIGNvbXByZW5kZXIgbGEgbmF0dXJhbGV6YSBkZSBsYXMgdmFyaWFibGVzIHkgbGEgZGlzdHJpYnVjacOzbiBkZSBsYXMgY2xhc2VzIChiZW5pZ25vIHkgbWFsaWdubykuIEFkZW3DoXMsIHNlIGlkZW50aWZpY2Fyw6EgeSBnZXN0aW9uYXLDoSBjdWFscXVpZXIgdmFsb3IgZmFsdGFudGUsIHkgc2UgZXZhbHVhcsOhIGxhIG5lY2VzaWRhZCBkZSByZWFsaXphciB0cmFuc2Zvcm1hY2lvbmVzIGFkaWNpb25hbGVzLCBjb21vIGxhIG5vcm1hbGl6YWNpw7NuIG8gbGEgY29udmVyc2nDs24gZGUgdmFyaWFibGVzIGNhdGVnw7NyaWNhcy4NCg0KMi4gICoqQXBsaWNhY2nDs24gZGUgTW9kZWxvcyBTdXBlcnZpc2Fkb3MqKlwNCiAgICBFbiBlc3RhIGV0YXBhLCBzZSBpbXBsZW1lbnRhcsOhbiB5IGVudHJlbmFyw6FuIHZhcmlvcyAqKm1vZGVsb3Mgc3VwZXJ2aXNhZG9zKiogcGFyYSBsYSBjbGFzaWZpY2FjacOzbiBkZSBsb3MgdHVtb3JlcyBjb21vIGJlbmlnbm9zIG8gbWFsaWdub3MuIExvcyBtb2RlbG9zIHNlbGVjY2lvbmFkb3MgaW5jbHV5ZW46DQoNCiAgICAtICAgKipDQVJUIChDbGFzc2lmaWNhdGlvbiBhbmQgUmVncmVzc2lvbiBUcmVlcykqKjogVW4gbW9kZWxvIGRlIMOhcmJvbCBkZSBkZWNpc2lvbmVzIHF1ZSBwZXJtaXRlIHJlYWxpemFyIHByZWRpY2Npb25lcyBtZWRpYW50ZSB1bmEgc2VyaWUgZGUgcmVnbGFzIGJhc2FkYXMgZW4gbG9zIGRhdG9zIGRlIGVudHJhZGEuDQogICAgLSAgICoqUmFuZG9tIEZvcmVzdCoqOiBVbiBjb25qdW50byBkZSDDoXJib2xlcyBkZSBkZWNpc2nDs24gcXVlIG1lam9yYSBsYSBwcmVjaXNpw7NuIG1lZGlhbnRlIGVsIHVzbyBkZSB0w6ljbmljYXMgZGUgbXVlc3RyZW8geSBjb21iaW5hY2nDs24gZGUgdmFyaW9zIG1vZGVsb3MuDQogICAgLSAgICoqU1ZNIChTdXBwb3J0IFZlY3RvciBNYWNoaW5lKSoqOiBVbiBtb2RlbG8gcXVlIGVuY3VlbnRyYSBlbCBoaXBlcnBsYW5vIMOzcHRpbW8gcXVlIHNlcGFyYSBsYXMgY2xhc2VzIGRlIG1hbmVyYSBlZmljYXouDQogICAgLSAgICoqUmVncmVzacOzbiBsb2fDrXN0aWNhKio6IFVuIG1vZGVsbyBlc3RhZMOtc3RpY28gcXVlIHB1ZWRlIGFkYXB0YXJzZSBhIGRpZmVyZW50ZXMgZGlzdHJpYnVjaW9uZXMgZGUgbGFzIHZhcmlhYmxlcyBkZXBlbmRpZW50ZXMsIGNvbW8gbGEgYmlub21pYWwgZW4gZXN0ZSBjYXNvLlwNCiAgICAgICAgU2UgZXZhbHVhcsOhIGxhICoqSW1wb3J0YW5jaWEgZGUgbGFzIFZhcmlhYmxlcyoqIGVuIGxvcyBtb2RlbG9zIHBhcmEgaWRlbnRpZmljYXIgbGFzIGNhcmFjdGVyw61zdGljYXMgbcOhcyBpbmZsdXllbnRlcyBlbiBsYXMgcHJlZGljY2lvbmVzLg0KICAgIC0gICoqUmVkZXMgTmV1cm9uYWxlcyoqOiBVbiBtb2RlbG8gZGUgYXByZW5kaXphamUgcHJvZnVuZG8gcXVlIHB1ZWRlIGNhcHR1cmFyIHJlbGFjaW9uZXMgY29tcGxlamFzIGVudHJlIGxhcyB2YXJpYWJsZXMgeSBhZGFwdGFyc2UgYSBwYXRyb25lcyBubyBsaW5lYWxlcyBlbiBsb3MgZGF0b3MuDQozLiAgKipFeHBlcmltZW50YWNpw7NuIGNvbiBlbCBEYXRhc2V0IFJlZHVjaWRvKipcDQogICAgUG9zdGVyaW9ybWVudGUsIHNlIHJlYWxpemFyw6EgdW5hICoqZXhwZXJpbWVudGFjacOzbioqIGVuIGxhIHF1ZSBzZSBtb2RpZmljYXLDoW4gbGFzIGNhcmFjdGVyw61zdGljYXMgZGVsIGRhdGFzZXQgbWVkaWFudGUgbGEgZWxpbWluYWNpw7NuIGRlIHZhcmlhYmxlcyBtZW5vcyByZWxldmFudGVzLCBiYXPDoW5kb3NlIGVuIGxhICoqaW1wb3J0YW5jaWEgZGUgbGFzIHZhcmlhYmxlcyoqIG9idGVuaWRhIGRlIGxvcyBtb2RlbG9zLiBFc3RvIHBlcm1pdGlyw6Egb3B0aW1pemFyIGxvcyBtb2RlbG9zIHkgcmVkdWNpciBlbCBzb2JyZWFqdXN0ZSwgbWVqb3JhbmRvIGxhIGNhcGFjaWRhZCBkZSBnZW5lcmFsaXphY2nDs24gZGVsIHNpc3RlbWEgZGUgZGlhZ27Ds3N0aWNvLg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KNC4gICoqQXBsaWNhY2nDs24gZGUgTW9kZWxvcyBObyBTdXBlcnZpc2Fkb3MqKlwNCiAgICBQYXJhIGV4cGxvcmFyIGxvcyBwYXRyb25lcyBzdWJ5YWNlbnRlcyBlbiBsb3MgZGF0b3Mgc2luIHV0aWxpemFyIGV0aXF1ZXRhcyBkZSBjbGFzZSwgc2UgaW1wbGVtZW50YXLDoW4gKiptb2RlbG9zIG5vIHN1cGVydmlzYWRvcyoqLiBFc3RvcyBpbmNsdWlyw6FuOg0KICAgIC0gICAqKkNsdXN0ZXJpbmcqKjogRW4gcHJpbWVyIGx1Z2FyIHNlIGJ1c2NhcsOhIGVsIG7Dum1lcm8gZGUgY2x1c3RlcnMgY29uIGVsICoqbcOpdG9kbyBkZWwgY29kbyoqIHkgKipzaWxob3VldHRlKiogU2UgdXRpbGl6YXLDoW4gdMOpY25pY2FzIGNvbW8gKipLLW1lYW5zKiosKipDbHVzdGVyaW5nIGplcsOhcnF1aWNvKiogbyAqKkRCU0NBTioqIHBhcmEgYWdydXBhciBsYXMgb2JzZXJ2YWNpb25lcyBkZSBhY3VlcmRvIGNvbiBzaW1pbGl0dWRlcyBlbiBzdXMgY2FyYWN0ZXLDrXN0aWNhcy4gRXN0byBwZXJtaXRpcsOhIGlkZW50aWZpY2FyIHBvc2libGVzIGdydXBvcyBkZSBkYXRvcyBxdWUgcG9kcsOtYW4gbm8gaGFiZXIgc2lkbyBldmlkZW50ZXMgZW4gbGEgY2xhc2lmaWNhY2nDs24gc3VwZXJ2aXNhZGEuDQogICAgLSAgICoqUmVkdWNjacOzbiBkZSBEaW1lbnNpb25hbGlkYWQqKjogUGFyYSByZWR1Y2lyIGVsIG7Dum1lcm8gZGUgdmFyaWFibGVzIHkgc2ltcGxpZmljYXIgZWwgYW7DoWxpc2lzLCBzZSBlbXBsZWFyw6FuIHTDqWNuaWNhcyBjb21vICoqUENBIChQcmluY2lwYWwgQ29tcG9uZW50IEFuYWx5c2lzKSB5IFQtU05FKiouIEVzdGFzIHTDqWNuaWNhcyBheXVkYXLDoW4gYSBkZXNjdWJyaXIgbGFzIGVzdHJ1Y3R1cmFzIGxhdGVudGVzIGVuIGxvcyBkYXRvcyB5IGZhY2lsaXRhcsOhbiBsYSB2aXN1YWxpemFjacOzbiB5IGVsIGFuw6FsaXNpcyBwb3N0ZXJpb3IuDQogICAgLSAgICoqQWxnb3JpdG1vIGEgcHJpb3JpKio6IFNlIGFwbGljYXLDoSBlbCBhbGdvcml0bW8gKiphIHByaW9yaSoqIHBhcmEgaWRlbnRpZmljYXIgcmVnbGFzIGRlIGFzb2NpYWNpw7NuIGVudHJlIGxhcyBjYXJhY3RlcsOtc3RpY2FzIGRlbCBkYXRhc2V0LiBFc3RvIHBlcm1pdGlyw6EgZGVzY3VicmlyIHBhdHJvbmVzIGludGVyZXNhbnRlcyB5IHJlbGFjaW9uZXMgb2N1bHRhcyBlbnRyZSBsYXMgdmFyaWFibGVzLg0KNS4gICoqUHJlc2VudGFjacOzbiBkZSBsb3MgUmVzdWx0YWRvcyB5IENvbmNsdXNpb25lcyoqXA0KICAgIEZpbmFsbWVudGUsIHNlIHByZXNlbnRhcsOhbiBsb3MgKipyZXN1bHRhZG9zKiogb2J0ZW5pZG9zIGRlIGxvcyBtb2RlbG9zIGV2YWx1YWRvcywgaW5jbHV5ZW5kbyB0YWJsYXMsIGdyw6FmaWNvcyB5IGFuw6FsaXNpcyBjb21wYXJhdGl2b3MuIEVuIGVzdGEgc2VjY2nDs24gc2UgZGlzY3V0aXLDoSBsYSBlZmVjdGl2aWRhZCBkZSBsYXMgdMOpY25pY2FzIGVtcGxlYWRhcywgZGVzdGFjYW5kbyBsb3MgbW9kZWxvcyBtw6FzIHByZWNpc29zIHkgbG9zIGVuZm9xdWVzIHF1ZSBtZWpvciBzZSBhanVzdGFuIGEgbGFzIG5lY2VzaWRhZGVzIGRlbCBkaWFnbsOzc3RpY28gZGUgY8OhbmNlciBkZSBtYW1hLiBMYXMgKipjb25jbHVzaW9uZXMqKiBzZSBjZW50cmFyw6FuIGVuIGxhIHZpYWJpbGlkYWQgeSBsb3MgcG9zaWJsZXMgcGFzb3MgYSBzZWd1aXIgcGFyYSBtZWpvcmFyIGVsIHJlbmRpbWllbnRvIGRlbCBtb2RlbG8geSBzdSBhcGxpY2FiaWxpZGFkIGVuIHVuIGVudG9ybm8gY2zDrW5pY28uDQoNCiMjIDMuIEltcGxlbWVudGFjacOzbg0KDQpFbiBlc3RhIHNlY2Npw7NuIHNlIG11ZXN0cmFuIGxvcyBwYXNvcyBxdWUgc2UgaGFuIHNlZ3VpZG8gcGFyYSBsYSByZWFsaXphY2nDs24gZGVsIHByb3llY3RvLiBJbmNsdXllIGVsIEFuw6FsaXNpcyBJbmljaWFsIHkgUHJlcHJvY2VzYW1pZW50byBkZSBsb3MgRGF0b3MsIGxvcyBtb2RlbG9zIHN1cGVydmlzYWRvcyB5IG5vIHN1cGVydmlzYWRvcyB1dGlsaXphZG9zLg0KDQojIyMgMy4xLiBBbsOhbGlzaXMgSW5pY2lhbCB5IFByZXByb2Nlc2FtaWVudG8gZGUgbG9zIERhdG9zLg0KDQojIyMjIDMuMS4xLiBJbXBvcnRhY2nDs24gZGUgbGlicmVyw61hcw0KDQpTZSBpbnN0YWxhbiBsb3MgcGFxdWV0ZXMgcXVlIHNlcsOhbiBuZWNlc2FyaW9zIGR1cmFudGUgZWwgcHJveWVjdG86DQoNCi0gICBUaWR5dmVyc2U6IFBhcmEgbGEgbWFuaXB1bGFjacOzbiBkZSBkYXRvcyB5IGdyw6FmaWNvcy4NCi0gICBDYXJldDogUGFyYSBlbCBwcmVwcm9jZXNhbWllbnRvIHkgbW9kZWxhZG8gTGF0dGljZSBlcyByZXF1ZXJpZG8gcG9yIENhcmV0DQotICAgRGF0YUV4cGxvcmVyOiBQYXJhIGxhIGV4cGxvcmFjacOzbiBhdXRvbWF0aXphZGEgZGUgbG9zIGRhdG9zLg0KLSAgIERwbHlyOiBQcm9wb3JjaW9uYSB1bmEgZ3JhbcOhdGljYSBkZSBtYW5pcHVsYWNpw7NuIGRlIGRhdG9zLg0KLSAgIEdncGxvdDI6IFBlcnNvbmFsaXphY2nDs24gZGUgZ3LDoWZpY2FzLg0KLSAgIFBzeWNoOiBQYXJhIGFuw6FsaXNpcyBlc3RhZMOtc3RpY28NCi0gICBDb3JycGxvdDogVmlzdWFsaXphY2nDs24gZGUgbWF0cml6IGRlIGNvcnJlbGFjacOzbi4NCi0gICBDYXI6ICBQYXJhIGNvbXBsZW1lbnRhciB0w6ljbmljYXMgZGUgcmVncmVzacOzbi4NCi0gICBwUm9jOiBQcm9wb3JjaW9uYSBoZXJyYW1pZW50YXMgcGFyYSB2aXN1YWxpemFyLiwgc3Vhdml6YXIgeSBjb21wYXJhciBjdXJ2YXMgUm9jLg0KDQpgYGB7ciBzZXR1cCwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0NCiMgTm90YTogRGVzY29tZW50YXIgbGFzIGzDrW5lYXMgZGUgaW5zdGFsYWNpw7NuIHNpIG5vIHNlIHRpZW5lbiBsb3MgcGFxdWV0ZXMgaW5zdGFsYWRvcy4gQ29tYW5kbyBjdHJsK3NoaWZ0K2MgcGFyYSBkZXNjb21lbnRhci4NCg0KIyBpbnN0YWxsLnBhY2thZ2VzKCJ0aWR5dmVyc2UiKQ0KIyBpbnN0YWxsLnBhY2thZ2VzKCJjYXJldCIpDQojIGluc3RhbGwucGFja2FnZXMoIkRhdGFFeHBsb3JlciIpDQojIGluc3RhbGwucGFja2FnZXMoImRwbHlyIikNCiMgaW5zdGFsbC5wYWNrYWdlcygiZ2dwbG90MiIpDQojIGluc3RhbGwucGFja2FnZXMoImxhdHRpY2UiKQ0KIyBpbnN0YWxsLnBhY2thZ2VzKCJwc3ljaCIpDQojIGluc3RhbGwucGFja2FnZXMoImNvcnJwbG90IikNCiMgaW5zdGFsbC5wYWNrYWdlcygiZ2djb3JycGxvdCIpDQojIGluc3RhbGwucGFja2FnZXMoImNhciIpDQojIGluc3RhbGwucGFja2FnZXMoInBST0MiKQ0KIyBpbnN0YWxsLnBhY2thZ2VzKCJmbGV4dGFibGUiKQ0KIyBpbnN0YWxsLnBhY2thZ2VzKCJrZXJubGFiIikNCiMgaW5zdGFsbC5wYWNrYWdlcygiY2x1c3RlciIpDQojIGluc3RhbGwucGFja2FnZXMoImRic2NhbiIpDQojIGluc3RhbGwucGFja2FnZXMoIlJ0c25lIikNCiMgaW5zdGFsbC5wYWNrYWdlcygiYXJ1bGVzIikNCiMgaW5zdGFsbC5wYWNrYWdlcygiYXJ1bGVzVml6IikNCmBgYA0KDQpgYGB7cn0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShjYXJldCkNCmxpYnJhcnkoRGF0YUV4cGxvcmVyKQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkobGF0dGljZSkNCmxpYnJhcnkocHN5Y2gpDQpsaWJyYXJ5KGNvcnJwbG90KQ0KbGlicmFyeShnZ2NvcnJwbG90KQ0KbGlicmFyeShwUk9DKQ0KbGlicmFyeShjYXIpDQpsaWJyYXJ5KGZsZXh0YWJsZSkNCmxpYnJhcnkoa2VybmxhYikNCmxpYnJhcnkoY2x1c3RlcikNCmxpYnJhcnkoZGJzY2FuKQ0KbGlicmFyeShSdHNuZSkNCmxpYnJhcnkoYXJ1bGVzKQ0KbGlicmFyeShhcnVsZXNWaXopDQpgYGANCg0KIyMjIyAzLjEuMi4gQ2FyZ2EgeSB2aXN1YWxpemFjacOzbiBkZSBkYXRvcw0KDQpQYXJhIGNvbWVuemFyIGVsIGFuw6FsaXNpcyBlcyBuZWNlc2FyaW8gcmVhbGl6YXIgbGEgY2FyZ2EgZGUgbG9zIGRhdG9zLCBzZSB2aXN1YWxpemFuIGxhcyBwcmltZXJhcyBmaWxhcyB5IGxhIGVzdHJ1Y3R1cmEgZGUgbnVlc3RybyBkYXRhc2V0Lg0KDQpgYGB7cn0NCmRhdGEgPC0gcmVhZC5jc3YoImRhdGEvZGF0YS5jc3YiKQ0KDQpoZWFkKGRhdGEpDQpgYGANCg0KDQoNCmBgYHtyfQ0KZGltIDwtIGRpbShkYXRhKQ0KDQpjYXQoIk7Dum1lcm8gZGUgY29sdW1uYXM6IiwgZGltWzJdLCAiXG4iKQ0KY2F0KCJOw7ptZXJvIGRlIGZpbGFzOiIsIGRpbVsxXSwgIlxuIikNCmBgYA0KQ29tbyBzZSBwdWVkZSBvYnNlcnZhciwgZWwgZGF0YXNldCBjdWVudGEgY29uIDU2OSBmaWxhcyB5IDMzIGNvbHVtbmFzLg0KDQoNCkEgY29udGludWFjacOzbiwgc2UgbXVlc3RyYSBsYSBlc3RydWN0dXJhLCB0aXBvcyB5IGFsZ3Vub3MgZWplbXBsb3MgZGUgbGFzIHZhcmlhYmxlcyBkZWwgZGF0YXNldCBwYXJhIHRlbmVyIHVuYSB2aXNpw7NuIGdsb2JhbCBkZWwgbGFzIGRpc3RpbnRhcyB2YXJpYWJsZXMgY29uIGxhcyBxdWUgc2UgY3VlbnRhLg0KDQpgYGB7cn0NCnN0cihkYXRhKQ0KYGBgDQoNCmBgYHtyfQ0KIyBzYXBwbHkgYXBsaWNhIHVuYSBmdW5jacOzbiBhIGNhZGEgY29sdW1uYSBkZWwgZGF0YWZyYW1lDQpjbGFzZXMgPC0gc2FwcGx5KGRhdGEsIGNsYXNzKQ0KY2xhc2VzX2RmIDwtIGRhdGEuZnJhbWUoQ2xhc2UgPSBjbGFzZXMpDQoNCnByaW50KGNsYXNlc19kZiwgcm93Lm5hbWVzID0gRkFMU0UpDQpgYGANClNlIG9idGllbmVuIGxhcyBjbGFzZXMgZGUgY2FkYSBjb2x1bW5hIGVuIGVsIGNvbmp1bnRvIGRlIGRhdG9zIHBhcmEgZW50ZW5kZXIgbWVqb3Igc3UgdGlwbyBkZSBkYXRvcyB5IGVzdHJ1Y3R1cmEuIEVzdG8gcGVybWl0ZSB2aXN1YWxpemFyIHLDoXBpZGFtZW50ZSBlbCB0aXBvIGRlIHZhcmlhYmxlIChudW3DqXJpY2EsIGNhdGVnw7NyaWNhLCBldGMuKSBxdWUgc2UgZW5jdWVudHJhIGVuIGVsIGRhdGFzZXQuDQoNCg0KU2UgdmlzdWFsaXphIHVuIHJlc3VtZW4gZXN0w6FkaXN0aWNvIGRlIGxvcyBkYXRvcy4NCg0KYGBge3J9DQpkZXNjcmliZShkYXRhKQ0KYGBgDQoNCg0KDQoNClNlIHZlcmlmaWNhIHNpIGV4aXN0ZW4gdmFsb3JlcyBmYWx0YW50ZXMgZW4gbG9zIGRhdG9zIHV0aWxpemFuZG8gbGEgZnVuY2nDs24gYGFueU5BKClgLiBFc3RvIHBlcm1pdGUgaWRlbnRpZmljYXIgcsOhcGlkYW1lbnRlIHNpIGhheSBkYXRvcyBpbmNvbXBsZXRvcyBlbiBlbCBkYXRhc2V0IHF1ZSBwdWVkYW4gcmVxdWVyaXIgbGltcGllemEgbyBtYW5lam8gZXNwZWNpYWwuDQoNCmBgYHtyfQ0KYW55TkEoZGF0YSkNCmBgYA0KDQpTZSBvYnNlcnZhIHF1ZSBleGlzdGVuIHZhbG9yZXMgZmFsdGFudGVzIGVuIGVsIGRhdGFzZXQuIFBhcmEgdmlzdWFsaXphciBkZSBtYW5lcmEgbcOhcyBjbGFyYSBsYSBjYW50aWRhZCBkZSB2YWxvcmVzIGZhbHRhbnRlcyBwb3IgY29sdW1uYSwgc2UgdXRpbGl6YSBsYSBmdW5jacOzbiBgcGxvdF9taXNzaW5nKGRhdGEpYC4NCg0KYGBge3J9DQpwbG90X21pc3NpbmcoZGF0YSkNCmBgYA0KDQpDb21vIHB1ZWRlIG9ic2VydmFyc2UsIHNlIGN1ZW50YSBjb24gNTY5IHZhbG9yZXMgZmFsdGFudGVzIGVuIGxhIMO6bHRpbWEgY29sdW1uYSAiWCIuIE3DoXMgYWRlbGFudGUgc2UgcHJvY2VkZXLDoSBhIGVsaW1pbmFyIGVzdGEgY29sdW1uYS4NCg0KIyMjIyAzLjEuMy4gQW7DoWxpc2lzIGV4cGxvcmF0b3JpbyBkZSBsb3MgZGF0b3MNCg0KRW4gZXN0ZSBwYXNvIGVsIG9iamV0aXZvIHNlcsOhIGVudGVuZGVyIGxhIGRpc3RyaWJ1Y2nDs24geSByZWxhY2lvbmVzIGRlIHZhcmlhYmxlcy4NCg0KIyMjIyMgMy4xLjMuMS4gVmlzdWFsaXphY2nDs24gZGUgZGlzdHJpYnVjaW9uZXMgeSBjb3JyZWxhY2lvbmVzOg0KDQpTZSB2aXN1YWxpemEgdW4gcmVzdW1lbiBncsOhZmljbyBnZW5lcmFsIHBhcmEgZW50ZW5kZXIgbGFzIGRpc3RyaWJ1Y2lvbmVzIHkgY29ycmVsYWNpb25lcyBlbnRyZSBsYXMgdmFyaWFibGVzIGVuIGVsIGNvbmp1bnRvIGRlIGRhdG9zLiBFc3RvIGF5dWRhIGEgaWRlbnRpZmljYXIgcGF0cm9uZXMsIGRpc3RyaWJ1Y2lvbmVzIGRlIGZyZWN1ZW5jaWEgeSBwb3NpYmxlcyByZWxhY2lvbmVzIGVudHJlIGxhcyBkaWZlcmVudGVzIGNvbHVtbmFzIGRlbCBkYXRhc2V0Lg0KDQpgYGB7cn0NCnBsb3RfaW50cm8oZGF0YSkNCmBgYA0KDQpDb21vIHNlIHB1ZWRlIG9ic2VydmFyLCBlbCBkYXRhc2V0IGN1ZW50YSBjb24gdW4gMyUgZGUgY29sdW1uYXMgZGlzY3JldGFzLCBlbiBjb25jcmV0byBsYSBjb2x1bW5hICJkaWFnbm9zaXMiIHF1ZSBlcyBsYSB2YXJpYWJsZSBvYmpldGl2bywgeSB1biA5NCUgZGUgY29sdW1uYXMgY29udGludWFzLiBBZGVtw6FzLCBzZSBvYnNlcnZhIHVuIDMlIGRlIGNvbHVtbmFzIGNvbiB2YWxvcmVzIGZhbHRhbnRlcywgcXVlIGNvcnJlc3BvbmRlbiBhIGxhIGNvbHVtbmEgIlgiLg0KDQpTZSB2aXN1YWxpemFuIGxhcyB2YXJpYWJsZXMgY2F0ZWfDs3JpY2FzIHF1ZSBkaWNlIHNpIGVsIHR1bW9yIGVzICJNYWxpZ25vIiBvICJCZW5pZ25vIiBwYXJhIGNvbnRhciBjdcOhbnRhcyBvYnNlcnZhY2lvbmVzIGhheSBkZSBjYWRhIHRpcG8uIEVzdG8gcGVybWl0ZSB2ZXIgbGEgZGlzdHJpYnVjacOzbiBkZSBsYSBjb2x1bW5hIGRpYWdub3NpcyB5IGVudGVuZGVyIGxhIHByb3BvcmNpw7NuIGVudHJlIGVsbGFzIGVuIGVsIGRhdGFzZXQuDQoNCmBgYHtyfQ0KZGlhZ25vc2lzX2NvdW50cyA8LSB0YWJsZShkYXRhJGRpYWdub3NpcykNCnBsb3RfYmFyKGRhdGEkZGlhZ25vc2lzKQ0KcHJpbnQoIkRpc3RyaWJ1Y2nDs24gZGUgbGEgdmFyaWFibGUgJ2RpYWdub3NpcycgQmVuaWdubyAoQikgeSBNYWxpZ25vIChNKToiKQ0KcHJpbnQoZGlhZ25vc2lzX2NvdW50cykNCmBgYA0KDQpMYSBjb2x1bW5hICJkaWFnbm9zaXMiIGNvbnRpZW5lIGRvcyBjbGFzZXM6ICJCIiAoQmVuaWdubykgeSAiTSIgKE1hbGlnbm8pLiBMYSBkaXN0cmlidWNpw7NuIGRlIGxhcyBjbGFzZXMgZXMgZGVzaWd1YWwsIGNvbiAzNTcgb2JzZXJ2YWNpb25lcyBkZSBsYSBjbGFzZSAiQiIgeSAyMTIgb2JzZXJ2YWNpb25lcyBkZSBsYSBjbGFzZSAiTSIuIEVzdG8gaW5kaWNhIHVuIGRlc2VxdWlsaWJyaW8gbGlnZXJvIGVuIGxhIGRpc3RyaWJ1Y2nDs24gZGUgbGFzIGNsYXNlcy4NCg0KUGFyYSB2aXN1YWxpemFyIGxhIGRpc3RyaWJ1Y2nDs24gZGUgbGFzIHZhcmlhYmxlcyBudW3DqXJpY2FzLCBzZSB1dGlsaXphIGxhIGZ1bmNpw7NuIGBwbG90X2hpc3RvZ3JhbShkYXRhKWAuIEVzdG8gcGVybWl0ZSB2ZXIgbGEgZGlzdHJpYnVjacOzbiBkZSBjYWRhIHZhcmlhYmxlIGVuIGVsIGRhdGFzZXQgeSBjb21wcmVuZGVyIG1lam9yIGxhIHZhcmlhYmlsaWRhZCB5IHJhbmdvIGRlIHZhbG9yZXMgZGUgbGFzIGNhcmFjdGVyw61zdGljYXMuDQoNCmBgYHtyfQ0KcGxvdF9oaXN0b2dyYW0oZGF0YSkNCmBgYA0KDQpDb21vIHNlIHB1ZWRlIG9ic2VydmFyLCBsYXMgdmFyaWFibGVzIG51bcOpcmljYXMgcHJlc2VudGFuIGRpZmVyZW50ZXMgZGlzdHJpYnVjaW9uZXMgeSByYW5nb3MgZGUgdmFsb3Jlcy4gQWxndW5hcyB2YXJpYWJsZXMsIGNvbW8gImNvbmNhdmUucG9pbnRzX3dvcnN0IiwgcGFyZWNlbiB0ZW5lciB1bmEgZGlzdHJpYnVjacOzbiBzZXNnYWRhIGhhY2lhIGxhIGRlcmVjaGEsIG1pZW50cmFzIHF1ZSBvdHJhcywgY29tbyAic3ltbWV0cnlfbWVhbiIsIHBhcmVjZW4gdGVuZXIgdW5hIGRpc3RyaWJ1Y2nDs24gbcOhcyBzaW3DqXRyaWNhLiBFc3RhcyBkaWZlcmVuY2lhcyBlbiBsYXMgZGlzdHJpYnVjaW9uZXMgcHVlZGVuIHNlciDDunRpbGVzIHBhcmEgaWRlbnRpZmljYXIgcGF0cm9uZXMgeSByZWxhY2lvbmVzIGVudHJlIGxhcyB2YXJpYWJsZXMuDQoNCiMjIyMgMy4xLjQuIFByZXByb2Nlc2FtaWVudG8gZGUgbG9zIGRhdG9zDQoNCkVzIG5lY2VzYXJpbyByZWFsaXphciB1biBwcmVwcm9jZXNhbWllbnRvIGRlIGxvcyBkYXRvcyBwYXJhIGxpbXBpYXJsb3MgeSBwcmVwYXJhcmxvcyBwYXJhIGVsIGFuw6FsaXNpcyB5IG1vZGVsYWRvLiBFc3RvIGluY2x1eWUgbGEgZWxpbWluYWNpw7NuIGRlIHZhbG9yZXMgZmFsdGFudGVzLCBsYSBjb2RpZmljYWNpw7NuIGRlIHZhcmlhYmxlcyBjYXRlZ8OzcmljYXMgeSBsYSBzZWxlY2Npw7NuIGRlIGF0cmlidXRvcyByZWxldmFudGVzLg0KDQojIyMjIyAzLjEuNC4xLiBFbGltaW5hY2nDs24gZGUgdmFsb3JlcyBmYWx0YW50ZXMNCg0KUGFyYSBjb21lbnphciwgc2UgaGEgaWRlbnRpZmljYWRvIHF1ZSBoYXkgdW5hIGNvbHVtbmEgY29uIHRvZG9zIGxvcyB2YWxvcmVzIGZhbHRhbnRlcywgZXMgZGVjaXIsIE5BLiBFbCBwcmltZXIgcGFzbyBlbiBlbCBwcmVwcm9jZXNhbWllbnRvIHNlcsOhIGVsaW1pbmFyIGVzdGEgY29sdW1uYSAieCIuDQoNClNlIHZlcmlmaWNhIHF1ZSBsYSBjb2x1bW5hICJYIiBzZSBoYXlhIGVsaW1pbmFkbyBjb3JyZWN0YW1lbnRlIHkgcXVlIG5vIGhheWEgb3Ryb3MgdmFsb3JlcyBmYWx0YW50ZXMgZW4gZWwgZGF0YXNldC4NCg0KYGBge3J9DQpkYXRhIDwtIGRhdGEgJT4lIHNlbGVjdCgtWCkNCnBsb3RfbWlzc2luZyhkYXRhKQ0KYW55TkEoZGF0YSkNCmBgYA0KDQpObyBxdWVkYW4gdmFsb3JlcyBmYWx0YW50ZXMgZW4gZWwgZGF0YXNldCwgcG9yIGxvIHF1ZSBzZSBwcm9jZWRlcsOhIGEgZWxpbWluYXIgbGEgY29sdW1uYSAiaWQiLCB5YSBxdWUgbm8gYXBvcnRhIGluZm9ybWFjacOzbiByZWxldmFudGUuDQoNCmBgYHtyfQ0KZGF0YSA8LSBkYXRhICU+JSBzZWxlY3QoLWlkKQ0KDQpwcmludCgiQnVzcXVlZGEgZGUgY29sdW1uYSBpZCBlbiBlbCBkYXRhc2V0OiIpDQpwcmludCgiaWQiICVpbiUgY29sbmFtZXMoZGF0YSkpDQoNCmBgYA0KDQpDb21vIHNlIHB1ZWRlIG9ic2VydmFyLCBzZSBoYSBlbGltaW5hZG8gbGEgY29sdW1uYSAiaWQiIHkgc2UgaGEgdmVyaWZpY2FkbyBxdWUgbm8gaGF5IG3DoXMgdmFsb3JlcyBmYWx0YW50ZXMsIGV4Y2VwdG8gbG9zIHlhIGVsaW1pbmFkb3MgZW4gIlgiLg0KDQpEZXNwdcOpcyBkZSBlc3RvcyBwcm9jZXNvcywgY29udGFtb3MgY29uIHVuIGRhdGFzZXQgZGU6DQoNCmBgYHtyfQ0KZGltIDwtIGRpbShkYXRhKQ0KDQpjYXQoIk7Dum1lcm8gZGUgY29sdW1uYXM6IiwgZGltWzJdLCAiXG4iKQ0KY2F0KCJOw7ptZXJvIGRlIGZpbGFzOiIsIGRpbVsxXSwgIlxuIikNCmBgYA0KDQo1NjkgZmlsYXMgeSAzMSBjb2x1bW5hcy4NCg0KIyMjIyMgMy4xLjQuMi4gQ29kaWZpY2FjacOzbiBkZSB2YXJpYWJsZXMgQ2F0ZWfDs3JpY2FzDQoNCkFudGVyaW9ybWVudGUsIHZpbW9zIHF1ZSBudWVzdHJvIGRhdGFzZXQgY3VlbnRhIGNvbiB1bmEgw7puaWNhIGNvbHVtbmEgZGUgdmFsb3JlcyBjYXRlZ8Ozcmljb3MsICJEaWFnbm9zaXMiLCBjdXlvcyB2YWxvcmVzIHRpZW5lbiBlbCBzaWd1aWVudGUgc2lnbmlmaWNhZG86IC0gTSAobWFsaWduYW50KSAtIEIgKGJlbmlnbikNCg0KRWwgc2lndWllbnRlIHBhc28gZW4gZWwgcHJlcHJvY2VzYWRvIGRlIGRhdG9zIHNlcsOhIHBhc2FyIGVzdGEgY29sdW1uYSBhIG51bcOpcmljYSwgbG8gcXVlIHNlcsOhIG5lY2VzYXJpbyBwYXJhIGVzdHVkaWFyIGxhIGNvcnJlbGFjacOzbiBkZSB2YXJpYWJsZXMgZGUgbnVlc3RybyBkYXRhc2V0LiANCg0KQ29tbyBzb2xvIHNlIHByZXNlbnRhbiBkb3MgcG9zaWJsZXMgdmFsb3JlcyAoIk0iIHkgIkIiKSwgc2UgYXBsaWNhcsOhIENvZGlmaWNhY2nDs24gQmluYXJpYTogRWwgdmFsb3IgIk0iIHBhc2Fyw6EgYSBzZXIgMSB5IHZhbG9yICJCIiBwYXNhcsOhIGEgc2VyIDAuDQoNCg0KYGBge3J9DQoNCiNNIC0+IDE7IEIgLT4gMA0KZGF0YSRkaWFnbm9zaXMgPC0gaWZlbHNlKGRhdGEkZGlhZ25vc2lzID09ICJNIiwgMSwgMCkNCmBgYA0KDQpTZSB2ZXJpZmljYSBxdWUgbGEgY29sdW1uYSAiZGlhZ25vc2lzIiBzZSBoYXlhIGNvZGlmaWNhZG8gY29ycmVjdGFtZW50ZS4NCg0KYGBge3J9DQojIGltcHJpbWlyIGxvcyBwcmltZXJvcyB2YWxvcmVzIGRlIGxhIGNvbHVtbmEgZGlhZ25vc2lzLCBmb3JtYXRlYWRvDQpwcmludCgiVmFsb3JlcyBkZSBsYSBjb2x1bW5hICdkaWFnbm9zaXMnIGRlc3B1w6lzIGRlIGxhIGNvZGlmaWNhY2nDs246IikNCmRhdGEkZGlhZ25vc2lzDQpgYGANCg0KQSBjb250aW51YWNpw7NuLCBzZSB2ZXJpZmljYSBxdWUgdG9kYXMgbGFzIGNvbHVtbmFzIGRlbCBkYXRhc2V0IHNlYW4gbnVtw6lyaWNhcywgeWEgcXVlIGFsZ3Vub3MgbW9kZWxvcyBkZSBhcHJlbmRpemFqZSBhdXRvbcOhdGljbyByZXF1aWVyZW4gcXVlIHRvZGFzIGxhcyB2YXJpYWJsZXMgZGUgZW50cmFkYSBsbyBzZWFuLiBQYXJhIGVsbG8sIHNlIGlkZW50aWZpY2FuIGxhcyBjb2x1bW5hcyBjYXRlZ8OzcmljYXMgeSBzZSBjb21wcnVlYmEgcXVlIHRvZGFzIGxhcyBjb2x1bW5hcyBzZWFuIG51bcOpcmljYXMuDQoNCmBgYHtyfQ0KDQpjYXRlZ29yaWNhbF9jb2x1bW5zIDwtIHNhcHBseShkYXRhLCBpcy5mYWN0b3IpIHwgc2FwcGx5KGRhdGEsIGlzLmNoYXJhY3RlcikNCm5hbWVzKGRhdGEpW2NhdGVnb3JpY2FsX2NvbHVtbnNdDQoNCg0KYGBgDQoNCkVmZWN0aXZhbWVudGUsIHRvZGFzIGxhcyBjb2x1bW5hcyBhaG9yYSBzb24gbnVtw6lyaWNhcywgbG8gcXVlIGRhIHBhc28gYWwgc2lndWllbnRlIHB1bnRvIGVuIGVsIHByZXByb2Nlc2FtaWVudG8gZGUgZGF0b3MuDQoNCiMjIyMjIDMuMS40LjMuIEVzdHVkaW8gZGUgY29ycmVsYWNpw7NuLg0KDQpBbCB0ZW5lciBlbCBkYXRhc2V0IGxpbXBpbyBkZSB2YWxvcmVzIGZhbHRhbnRlcywgTkEsIHkgc29sbyBoYXkgcHJlc2VudGVzIHZhcmlhYmxlcyBudW3DqXJpY2FzLCBlbCBzaWd1aWVudGUgcGFzbyBzZXLDoSBlc3R1ZGlhciBsYXMgcG9zaWJsZXMgY29ycmVsYWNpb25lcyBkZSBudWVzdHJvIGRhdGFzZXQuDQoNCkVzdHVkaWFyIGxhIGNvcnJlbGFjacOzbiBkZSBsb3MgZGF0b3MgYXl1ZGEgYSBpZGVudGlmaWNhciBwYXRyb25lcyB5IHJlbGFjaW9uZXMgZW50cmUgdmFyaWFibGVzLCBsbyBxdWUgcG9kcsOtYSBjb25kdWNpciBhIG51ZXZhcyBoaXDDs3Rlc2lzIHkgZGVzY3VicmltaWVudG9zLiBBZGVtw6FzLCB1biBidWVuIGVzdHVkaW8gZGUgY29ycmVsYWNpb25lcyBwb2Ryw61hIHNlciDDunRpbCBwYXJhIHNlbGVjY2lvbmFyIHZhcmlhYmxlcyByZWxldmFudGVzIHkgY29uc3RydWlyIG1vZGVsb3MgZW4gdW4gZnV0dXJvLg0KDQpMb3MgdmFsb3JlcyBxdWUgc2Ugb2J0aWVuZW4gdGVuZHLDoW4gbGEgc2lndWllbnRlIGludGVycHJldGFjacOzbjoNCg0KLSAgIENvcnJlbGFjacOzbiBjZXJjYW5hIGEgMTogUmVsYWNpw7NuIHBvc2l0aXZhIGZ1ZXJ0ZS4NCi0gICBDb3JyZWxhY2nDs24gY2VyY2FuYSBhIC0xOiBSZWxhY2nDs24gbmVnYXRpdmEgZnVlcnRlLg0KLSAgIENvcnJlbGFjacOzbiBjZXJjYW5hIGEgMDogTm8gaGF5IHJlbGFjacOzbiBsaW5lYWwgc2lnbmlmaWNhdGl2YQ0KDQpTZSBjYWxjdWxhIGxhIG1hdHJpeiBkZSBjb3JyZWxhY2nDs24gcGFyYSBlbCBjb25qdW50byBkZSBkYXRvcyB1dGlsaXphbmRvIHNvbG8gbGFzIG9ic2VydmFjaW9uZXMgY29tcGxldGFzLiBFc3RvIHBlcm1pdGUgdmlzdWFsaXphciBsYXMgcmVsYWNpb25lcyBsaW5lYWxlcyBlbnRyZSBsYXMgdmFyaWFibGVzIG51bcOpcmljYXMgZGVsIGRhdGFzZXQsIGlkZW50aWZpY2FuZG8gcGF0cm9uZXMgZGUgY29ycmVsYWNpw7NuIHBvc2l0aXZhIHkgbmVnYXRpdmEgZW50cmUgbGFzIGRpZmVyZW50ZXMgdmFyaWFibGVzLg0KDQpgYGB7cn0NCg0KIyBDYWxjdWxhciBtYXRyaXogZGUgY29ycmVsYWNpw7NuDQpjb3JyZWxhdGlvbl9tYXRyaXggPC0gY29yKGRhdGEsIHVzZSA9ICJjb21wbGV0ZS5vYnMiKSAgIyBJZ25vcmEgdmFsb3JlcyBmYWx0YW50ZXMNCg0KDQojIEdyYWZpY2FyIG1hdHJpeiBkZSBjb3JyZWxhY2nDs24NCmNvcnJwbG90KGNvcnJlbGF0aW9uX21hdHJpeCwgbWV0aG9kID0gImNvbG9yIiwgdHlwZSA9ICJ1cHBlciIsIHRsLmNleCA9IDAuOCkNCg0KYGBgDQoNCkNvbW8gc2UgcHVlZGUgb2JzZXJ2YXIsIGRhZG8gcXVlIGVsIGNvbmp1bnRvIGRlIGRhdG9zIGNvbnRpZW5lIDMxIHZhcmlhYmxlcywgbGEgbWF0cml6IGRlIGNvcnJlbGFjacOzbiByZXN1bHRhIGRpZsOtY2lsIGRlIGludGVycHJldGFyIGRpcmVjdGFtZW50ZS4gUGFyYSBmYWNpbGl0YXIgbGEgY29tcHJlbnNpw7NuIGRlIGxhcyByZWxhY2lvbmVzIGVudHJlIGxhcyB2YXJpYWJsZXMsIHNlIG11ZXN0cmEgYSBjb250aW51YWNpw7NuIHVuYSB0YWJsYSBjb24gbGFzIHBhcmVqYXMgZGUgYXRyaWJ1dG9zIG9yZGVuYWRhcyBwb3IgbGEgbWFnbml0dWQgZGUgbGEgY29ycmVsYWNpw7NuLg0KDQpgYGB7cn0NCiMgRWxpbWluYXIgbGEgY29sdW1uYSAnZGlhZ25vc2lzJyBhbnRlcyBkZSBjYWxjdWxhciBsYXMgY29ycmVsYWNpb25lcw0KZGF0YV9ub190YXJnZXQgPC0gZGF0YVssICFuYW1lcyhkYXRhKSAlaW4lIGMoImRpYWdub3NpcyIpXQ0KDQpjb3JfbWF0cml4IDwtIGNvcihkYXRhX25vX3RhcmdldCwgdXNlID0gImNvbXBsZXRlLm9icyIpDQoNCmNvcl9tYXRyaXhfZGYgPC0gYXMuZGF0YS5mcmFtZShhcy50YWJsZShjb3JfbWF0cml4KSkNCmNvcl9tYXRyaXhfZGYgPC0gY29yX21hdHJpeF9kZltjb3JfbWF0cml4X2RmJFZhcjEgIT0gY29yX21hdHJpeF9kZiRWYXIyLCBdDQpjb3JfbWF0cml4X2RmIDwtIGNvcl9tYXRyaXhfZGZbb3JkZXIoLWFicyhjb3JfbWF0cml4X2RmJEZyZXEpKSwgXQ0KIyBFbGltaW5hciBkdXBsaWNhZG9zLiBhPC0+YiA9IGI8LT5hDQpjb3JfbWF0cml4X2RmIDwtIGNvcl9tYXRyaXhfZGZbc2VxKDEsIG5yb3coY29yX21hdHJpeF9kZiksIGJ5ID0gMiksIF0NCg0KcHJpbnQoY29yX21hdHJpeF9kZikNCmBgYA0KDQpFbiBsYSB0YWJsYSBzZSBwcmVzZW50YW4gbGFzIGNvcnJlbGFjaW9uZXMgZW50cmUgbGFzIHZhcmlhYmxlcyBpbmRlcGVuZGllbnRlcyBkZWwgY29uanVudG8gZGUgZGF0b3MuIFNlIG9ic2VydmFuIGNvcnJlbGFjaW9uZXMgYWx0YXMgZW50cmUgYWxndW5hcyBkZSBsYXMgdmFyaWFibGVzLCBjb21vICoqcGVyaW1ldGVyX21lYW4geSByYWRpdXNfbWVhbioqICgwLjk5OCksICoqcGVyaW1ldGVyX3dvcnN0IHkgcmFkaXVzX3dvcnN0KiogKDAuOTk0KSwgKiphcmVhX21lYW4geSByYWRpdXNfbWVhbioqICgwLjk4NyksIHkgKiphcmVhX3dvcnN0IHkgcGVyaW1ldGVyX3dvcnN0KiogKDAuOTc4KS4gRXN0YXMgYWx0YXMgY29ycmVsYWNpb25lcyB0aWVuZW4gc2VudGlkbyBwb3JxdWUgbGFzIHZhcmlhYmxlcyBkZSAqKnJhZGlvKiogKHJhZGl1cykgeSAqKnBlcsOtbWV0cm8qKiAocGVyaW1ldGVyKSBlc3TDoW4gbWF0ZW3DoXRpY2FtZW50ZSByZWxhY2lvbmFkYXM6IGEgbWVkaWRhIHF1ZSBlbCByYWRpbyBkZSB1biB0dW1vciBhdW1lbnRhLCB0YW1iacOpbiBsbyBoYWNlIHN1IHBlcsOtbWV0cm8sIGxvIHF1ZSBleHBsaWNhIGxhcyBjb3JyZWxhY2lvbmVzIGNlcmNhbmFzIGEgKioxKiouIFNpbWlsYXJtZW50ZSwgbGFzIHZhcmlhYmxlcyBkZSAqKsOhcmVhKiogdGFtYmnDqW4gZXN0w6FuIGZ1ZXJ0ZW1lbnRlIGNvcnJlbGFjaW9uYWRhcyBjb24gZWwgcmFkaW8geSBlbCBwZXLDrW1ldHJvLCB5YSBxdWUgdW4gdHVtb3IgY29uIHVuIG1heW9yIHJhZGlvIHN1ZWxlIHRlbmVyIHVuYSBtYXlvciDDoXJlYS4gQXVucXVlIGVzdGFzIGNvcnJlbGFjaW9uZXMgc29uIGFsdGFzLCBubyBzb24gc29ycHJlbmRlbnRlcywgeWEgcXVlIHRvZGFzIGVzdMOhbiBjYXB0dXJhbmRvIGRpZmVyZW50ZXMgYXNwZWN0b3MgZGVsICoqdGFtYcOxbyoqIHkgbGEgKipmb3JtYSoqIGRlbCB0dW1vci4NCg0KRGFkbyBxdWUgImRpYWdub3NpcyIgZXMgbnVlc3RyYSB2YXJpYWJsZSBvYmpldGl2bywgdmFtb3MgYSBvYnNlcnZhciBjw7NtbyBzZSBjb3JyZWxhY2lvbmFuIGxhcyBkZW3DoXMgdmFyaWFibGVzIGNvbiBlbGxhIHBhcmEgZW50ZW5kZXIgbWVqb3Igc3UgcmVsYWNpw7NuIHkgcG9zaWJsZXMgaW5mbHVlbmNpYXMuDQoNCmBgYHtyfQ0KY29yX3dpdGhfdGFyZ2V0IDwtIGNvcihkYXRhLCBkYXRhJGRpYWdub3NpcywgdXNlID0gImNvbXBsZXRlLm9icyIpDQpjb3JyZWxhdGlvbl9kZiA8LSBkYXRhLmZyYW1lKFZhcmlhYmxlID0gbmFtZXMoZGF0YSksIENvcnJlbGF0aW9uID0gY29yX3dpdGhfdGFyZ2V0KQ0KY29ycmVsYXRpb25fZGZfc29ydGVkIDwtIGNvcnJlbGF0aW9uX2RmW29yZGVyKC1jb3JyZWxhdGlvbl9kZiRDb3JyZWxhdGlvbiksIF0NCnByaW50KGNvcnJlbGF0aW9uX2RmX3NvcnRlZCkNCmBgYA0KDQpFbiBlc3RhIHRhYmxhIHNlIHByZXNlbnRhIGxhIGNvcnJlbGFjacOzbiBkZSBjYWRhIHZhcmlhYmxlIGNvbiBsYSB2YXJpYWJsZSBvYmpldGl2byAqKidkaWFnbm9zaXMnKiouIExhIGNvcnJlbGFjacOzbiBkZSAqKidkaWFnbm9zaXMnKiogY29uc2lnbyBtaXNtYSBlcyAqKjEqKiwgY29tbyBlcyBlc3BlcmFkby4gTGFzIHZhcmlhYmxlcyBxdWUgcHJlc2VudGFuIGxhcyBjb3JyZWxhY2lvbmVzIG3DoXMgYWx0YXMgY29uICoqJ2RpYWdub3NpcycqKiBzb24gKipjb25jYXZlLnBvaW50c193b3JzdCoqICgwLjc5MyksICoqcGVyaW1ldGVyX3dvcnN0KiogKDAuNzgyKSwgKipjb25jYXZlLnBvaW50c19tZWFuKiogKDAuNzc2KSwgKipyYWRpdXNfd29yc3QqKiAoMC43NzYpIHkgKipwZXJpbWV0ZXJfbWVhbioqICgwLjc0MykuIEVzdGFzIGNvcnJlbGFjaW9uZXMgaW5kaWNhbiBxdWUgbGFzIGNhcmFjdGVyw61zdGljYXMgcmVsYWNpb25hZGFzIGNvbiBlbCAqKm7Dum1lcm8gZGUgcHVudG9zIGPDs25jYXZvcyoqIHkgZWwgKipwZXLDrW1ldHJvKiogZGVsIHR1bW9yIGVzdMOhbiBmdWVydGVtZW50ZSBhc29jaWFkYXMgY29uIGVsIGRpYWduw7NzdGljbywgbG8gcXVlIGxhcyBjb252aWVydGUgZW4gYnVlbm9zIHByZWRpY3RvcmVzIGRlbCBkaWFnbsOzc3RpY28gbWFsaWdubyBvIGJlbmlnbm8uIEVuIGdlbmVyYWwsIGxhcyB2YXJpYWJsZXMgcXVlIHByZXNlbnRhbiBjb3JyZWxhY2lvbmVzIHN1cGVyaW9yZXMgYSAqKjAuNyoqIHN1Z2llcmVuIHF1ZSBzb24gcmVsZXZhbnRlcyBwYXJhIGxhIHByZWRpY2Npw7NuIGRlIGxhIHZhcmlhYmxlIG9iamV0aXZvLiBFc3RhcyBjYXJhY3RlcsOtc3RpY2FzIGRlbCAqKnRhbWHDsW8qKiB5IGxhICoqZm9ybWEqKiBkZWwgdHVtb3IsIGNvbW8gZWwgcGVyw61tZXRybyB5IGxvcyBwdW50b3MgY8OzbmNhdm9zLCBzb24gZXNwZWNpYWxtZW50ZSBpbXBvcnRhbnRlcyBwYXJhIHByZWRlY2lyIHNpIGVsIHR1bW9yIGVzIG1hbGlnbm8gbyBiZW5pZ25vLg0KDQpMYXMgdmFyaWFibGVzIG1lbm9zIGNvcnJlbGFjaW9uYWRhcyBjb24gKionZGlhZ25vc2lzJyoqIHNvbiAqKnN5bW1ldHJ5X3NlKiogKC0wLjAwNiksICoqZnJhY3RhbF9kaW1lbnNpb25fc2UqKiAoLTAuMDc3KSwgKip0ZXh0dXJlX3NlKiogKC0wLjAwOCksICoqc21vb3RobmVzc19zZSoqICgtMC4wNjcpIHkgKipmcmFjdGFsX2RpbWVuc2lvbl9tZWFuKiogKC0wLjAxMikuIEVzdGFzIGNvcnJlbGFjaW9uZXMgY2VyY2FuYXMgYSAqKjAqKiBpbmRpY2FuIHF1ZSBlc3RhcyBjYXJhY3RlcsOtc3RpY2FzIG5vIGVzdMOhbiBmdWVydGVtZW50ZSBhc29jaWFkYXMgY29uIGVsIGRpYWduw7NzdGljbyB5IHB1ZWRlbiBubyBzZXIgdGFuIHJlbGV2YW50ZXMgcGFyYSBsYSBwcmVkaWNjacOzbi4gRXN0byB0aWVuZSBzZW50aWRvIHlhIHF1ZSBzb24gZXJyb3JlcyBlc3TDoW5kYXIgeSBtZWRpZGFzIGRlIHRleHR1cmEgeSBzdWF2aWRhZCBxdWUgcG9kcsOtYW4gbm8gc2VyIHRhbiBpbmZvcm1hdGl2YXMgcGFyYSBkaXN0aW5ndWlyIGVudHJlIHR1bW9yZXMgbWFsaWdub3MgeSBiZW5pZ25vcy4NCg0KIyMjIyMgMy4xLjQuNC4gU2VsZWNjacOzbiBkZSBBdHJpYnV0b3MNCg0KTGEgc2VsZWNjacOzbiBkZSBhdHJpYnV0b3Mgc2UgcmVmaWVyZSBhbCBwcm9jZXNvIGRlIGlkZW50aWZpY2FyIHkgZWxlZ2lyIGxhcyB2YXJpYWJsZXMgbcOhcyByZWxldmFudGVzIHBhcmEgdW4gYW7DoWxpc2lzLCBkZXNjYXJ0YW5kbyBsYXMgcmVkdW5kYW50ZXMgbyBpcnJlbGV2YW50ZXMgcGFyYSBtZWpvcmFyIGxhIGNhbGlkYWQgeSBlZmljaWVuY2lhIGRlbCBtb2RlbG8uIEVuIGVzdGUgY2FzbywgZGFkbyBxdWUgYWxndW5hcyB2YXJpYWJsZXMgZW4gZWwgZGF0YXNldCBtdWVzdHJhbiBhbHRhcyBjb3JyZWxhY2lvbmVzIGVudHJlIHPDrSwgc2UgcG9kcsOtYSBjb25zaWRlcmFyIGVsaW1pbmFybGFzIHBhcmEgcmVkdWNpciBsYSBkaW1lbnNpb25hbGlkYWQgeSBzaW1wbGlmaWNhciBlbCBhbsOhbGlzaXMuDQoNCkEgcGVzYXIgZGUgbGFzIGNvcnJlbGFjaW9uZXMgZnVlcnRlcyBlbnRyZSBjaWVydGFzIHZhcmlhYmxlcywgbmluZ3VuYSBlcyBtYXlvciBkZSAwLDgtMC45IHBvciBsbyBxdWUgZWxpbWluYXJsYXMgcG9kcsOtYSBubyBzZXIgYWRlY3VhZG8geWEgcXVlIGVsIGNvbmp1bnRvIGRlIGRhdG9zIGVzIGxpbWl0YWRvLiBMYSBlbGltaW5hY2nDs24gZGUgdmFyaWFibGVzIHBvZHLDrWEgbGxldmFyIGEgdW5hIHDDqXJkaWRhIHNpZ25pZmljYXRpdmEgZGUgaW5mb3JtYWNpw7NuIHZhbGlvc2EgcXVlIHBvZHLDrWEgc2VyIGNydWNpYWwgcGFyYSBlbCBhbsOhbGlzaXMgeSBsYXMgcHJlZGljY2lvbmVzLiBNYW50ZW5lciB0b2RhcyBsYXMgdmFyaWFibGVzIGNvbW8gImFyZWFfbWVhbiIgbyAicGVyaW1ldGVyX21lYW4iIGFwb3J0YSBpbmZvcm1hY2nDs24gw7puaWNhIHkgdmFsaW9zYSBhbCBtb2RlbG8sIGxvIHF1ZSBwZXJtaXRlIGNhcHR1cmFyIHVuIHBhbm9yYW1hIG3DoXMgY29tcGxldG8gZGVsIHR1bW9yLg0KDQpQb3IgbG8gdGFudG8sIHNlIG9wdGFyw6EgcG9yIG1hbnRlbmVyIHRvZGFzIGxhcyB2YXJpYWJsZXMuIEHDum4gYXPDrSwgZW4gbGEgZXhwZXJpbWVudGFjacOzbiBjb24gbG9zIG1vZGVsb3Mgc2UgZXZhbHVhcsOhIHNpIGxhIGVsaW1pbmFjacOzbiBkZSBhbGd1bmFzIHZhcmlhYmxlcyByZWR1bmRhbnRlcyBvIGlycmVsZXZhbnRlcyBwdWVkZSBtZWpvcmFyIGxhIHByZWNpc2nDs24geSBsYSBlZmljaWVuY2lhIGRlbCBtb2RlbG8uDQoNCiMjIyAzLjIuIEFuw6FsaXNpcyBTdXBlcnZpc2Fkby4NCg0KRWwgYXByZW5kaXphamUgc3VwZXJ2aXNhZG8sIHRhbWJpw6luIGNvbm9jaWRvIGNvbW8gbWFjaGluZSBsZWFybmluZyBzdXBlcnZpc2FkbywgZXMgdW5hIHN1YmNhdGVnb3LDrWEgZGVsIG1hY2hpbmUgbGVhcm5pbmcgeSBsYSBpbnRlbGlnZW5jaWEgYXJ0aWZpY2lhbC4gU2UgZGVmaW5lIHBvciBlbCB1c28gZGUgY29uanVudG9zIGRlIGRhdG9zIGV0aXF1ZXRhZG9zIHBhcmEgZW50cmVuYXIgYWxnb3JpdG1vcyBxdWUgY2xhc2lmaWNhbiBsb3MgZGF0b3MgbyBwcmVkaWNlbiBsb3MgcmVzdWx0YWRvcyBjb24gcHJlY2lzacOzbi4NCg0KQSBtZWRpZGEgcXVlIHNlIGludHJvZHVjZW4gZGF0b3MgZW4gZWwgbW9kZWxvLCBlc3RlIGFqdXN0YSBzdXMgcG9uZGVyYWNpb25lcyBpdGVyYXRpdmFtZW50ZSBoYXN0YSBxdWUgc2UgaGEgYWxjYW56YWRvIHVuIGFqdXN0ZSBhZGVjdWFkbywgcHJvY2VzbyBxdWUgb2N1cnJlIGNvbW8gcGFydGUgZGUgbGEgdmFsaWRhY2nDs24gY3J1emFkYS4NCg0KRW4gZXN0ZSBhbsOhbGlzaXMsIGxhIHZhcmlhYmxlIGEgcHJlZGVjaXIgc2Vyw6EgImRpYWdub3NpcyIsIHF1ZSBjb21vIHNlIG1lbmNpb27DsyBwcmV2aWFtZW50ZSwgZXMgYmluYXJpYTogMSBzaSBlbCB0dW1vciBlcyBtYWxpZ25vIHkgMCBzaSBlcyBiZW5pZ25vLg0KDQpTZSBldmFsdWFyb24gZGlmZXJlbnRlcyBtb2RlbG9zIGRlIGNsYXNpZmljYWNpw7NuIHBhcmEgdW4gY29uanVudG8gZGUgZGF0b3MgY29uIDU2OSBtdWVzdHJhcyB5IDMwIHZhcmlhYmxlcywgY2F0ZWdvcml6YWRvcyBlbiAnQmVuaWdubycgeSAnTWFsaWdubycuIExvcyBtb2RlbG9zIGFuYWxpemFkb3MgaW5jbHV5ZW4gZWwgw4FyYm9sIGRlIERlY2lzacOzbiAoQ0FSVCksIFJhbmRvbSBGb3Jlc3QgKEJvc3F1ZSBBbGVhdG9yaW8pLCBNw6FxdWluYXMgZGUgU29wb3J0ZSBWZWN0b3JpYWwgY29uIG7DumNsZW8gUmFkaWFsIChTVk0pLCBSZWdyZXNpw7NuIGxvZ8Otc3RpY2EgeSB1biBjbGFzaWZpY2Fkb3IgY29uIHJlZGVzIG5ldXJvbmFsZXMuDQoNCkVsIHByaW1lciBwYXNvIGVuIGVsIGFuw6FsaXNpcyBzdXBlcnZpc2FkbyBlcyByZWFsaXphciBsYSBkaXZpc2nDs24gZGVsIGRhdGFzZXQuDQoNCiMjIyMgMy4yLjEuIERpdmlzacOzbiBkZWwgRGF0YXNldDoNCg0KUGFyYSBlc3RlIHBhc28sIHNlIHV0aWxpemFyw6EgbGEgdmFsaWRhY2nDs24gY3J1emFkYSBLLWZvbGQgcGFyYSBkaXZpZGlyIGVsIGRhdGFzZXQgeSBldmFsdWFyIGVsIG1vZGVsby4gRW4gbHVnYXIgZGUgdW5hIGRpdmlzacOzbiBzaW1wbGUgZW4gZW50cmVuYW1pZW50byB5IHBydWViYSwgSy1mb2xkIGFzZWd1cmEgcXVlIGNhZGEgc3ViY29uanVudG8gbyBmb2xkIGRlbCBkYXRhc2V0IHNlYSB1dGlsaXphZG8gdGFudG8gcGFyYSBlbCBlbnRyZW5hbWllbnRvIGNvbW8gcGFyYSBsYSBwcnVlYmEgZW4gZGlmZXJlbnRlcyBpdGVyYWNpb25lcywgcHJvcG9yY2lvbmFuZG8gYXPDrSB1bmEgZXZhbHVhY2nDs24gbcOhcyByb2J1c3RhLg0KDQpFc3RhYmxlY2Vtb3MgdW5hIHNlbWlsbGEgcGFyYSBhc2VndXJhciBxdWUgbG9zIHJlc3VsdGFkb3Mgc2VhbiByZXByb2R1Y2libGVzLCBlcyBkZWNpciwgcXVlIHNlIG9idGVuZ2FuIGxvcyBtaXNtb3MgcmVzdWx0YWRvcyBjYWRhIHZleiBxdWUgc2UgZWplY3V0ZSBlbCBjw7NkaWdvLiBMdWVnbywgY29uZmlndXJhbW9zIGVsIEstZm9sZCBDcm9zcy1WYWxpZGF0aW9uIGNvbiBwcm9iYWJpbGlkYWRlcyBkZSBjbGFzZSwgcXVlIG5vcyBwZXJtaXRpcsOhIGV2YWx1YXIgbG9zIG1vZGVsb3MgdXRpbGl6YW5kbyBtw6l0cmljYXMgZGUgY2xhc2lmaWNhY2nDs24gYmluYXJpYS4NCg0KYGBge3J9DQpzZXQuc2VlZCgxMjMpDQoNCmsgPC0gNQ0KDQp0cmFpbl9jb250cm9sIDwtIHRyYWluQ29udHJvbCgNCiAgbWV0aG9kID0gImN2IiwgICAgICAgICAgICMgQ3Jvc3MtdmFsaWRhdGlvbg0KICBudW1iZXIgPSBrLCAgICAgICAgICAgICAgIyBOw7ptZXJvIGRlIHBsaWVndWVzIChmb2xkcykNCiAgY2xhc3NQcm9icyA9IFRSVUUsICAgICAgICMgSGFiaWxpdGFyIHByb2JhYmlsaWRhZGVzIGRlIGNsYXNlDQogIHN1bW1hcnlGdW5jdGlvbiA9IHR3b0NsYXNzU3VtbWFyeSwgIyBQYXJhIG3DqXRyaWNhcyBkZSBjbGFzaWZpY2FjacOzbiBiaW5hcmlhDQogIHNhdmVQcmVkaWN0aW9ucyA9ICJmaW5hbCIgIyBHdWFyZGFyIGxhcyBwcmVkaWNjaW9uZXMgZmluYWxlcw0KKQ0KDQpgYGANCg0KU2UgY2FtYmlhbiBsb3Mgbml2ZWxlcyBkZSBsYSB2YXJpYWJsZSBvYmpldGl2byAiZGlhZ25vc2lzIiBkZSAwIHkgMSBhICJCIiB5ICJNIiBwYXJhIGZhY2lsaXRhciBsYSBpbnRlcnByZXRhY2nDs24gZGUgbG9zIHJlc3VsdGFkb3MgeSBsYSB2aXN1YWxpemFjacOzbiBkZSBsYXMgbcOpdHJpY2FzIGRlIGV2YWx1YWNpw7NuLg0KDQpgYGB7cn0NCmRhdGEkZGlhZ25vc2lzIDwtIGZhY3RvcihkYXRhJGRpYWdub3NpcywgbGV2ZWxzID0gYygwLCAxKSwgbGFiZWxzID0gYygiQiIsICJNIikpDQpgYGANCg0KUGFyYSBwb2RlciBldmFsdWFyIGVsIHJlbmRpbWllbnRvIGRlIGxvcyBtb2RlbG9zLCBlcyBuZWNlc2FyaW8gZXN0YWJsZWNlciB1biBwdW50byBkZSByZWZlcmVuY2lhIG8gKmJhc2VsaW5lKiBxdWUgbm9zIHBlcm1pdGEgY29tcGFyYXIgc3UgZGVzZW1wZcOxby4gRW4gZXN0ZSBjYXNvLCB1dGlsaXphcmVtb3MgdW4gZW5mb3F1ZSBzaW1wbGUgYmFzYWRvIGVuIGxhIGNsYXNlIG1heW9yaXRhcmlhLCBxdWUgY29uc2lzdGUgZW4gcHJlZGVjaXIgbGEgY2xhc2UgbcOhcyBjb23Dum4gZW4gZWwgY29uanVudG8gZGUgZGF0b3Mgc2luIHRlbmVyIGVuIGN1ZW50YSBuaW5ndW5hIGNhcmFjdGVyw61zdGljYSBwcmVkaWN0aXZhLiBFc3RlIGVuZm9xdWUgbm8gZXMgw7p0aWwgcGFyYSBsYSBwcmVkaWNjacOzbiByZWFsLCBwZXJvIG5vcyBwcm9wb3JjaW9uYXLDoSB1bmEgcmVmZXJlbmNpYSBtw61uaW1hIHBhcmEgZXZhbHVhciBsYSBlZmljYWNpYSBkZSBsb3MgbW9kZWxvcyBwb3N0ZXJpb3Jlcy4NCg0KRW4gbnVlc3RybyBhbsOhbGlzaXMsIGxhIGNsYXNlIG1heW9yaXRhcmlhIHNlIGlkZW50aWZpY2EgY29tbyAiQmVuaWdubyIgKEIpLiBFc3RvIHNpZ25pZmljYSBxdWUsIHNpIHV0aWxpesOhcmFtb3MgZXN0ZSBlbmZvcXVlLCB0b2RhcyBsYXMgaW5zdGFuY2lhcyBzZSBjbGFzaWZpY2Fyw61hbiBjb21vIGJlbmlnbmFzLCBpZ25vcmFuZG8gY3VhbHF1aWVyIGNhcmFjdGVyw61zdGljYSBkZWwgY29uanVudG8gZGUgZGF0b3MgcXVlIHB1ZGllcmEgZGlmZXJlbmNpYXIgbG9zIGNhc29zIG1hbGlnbm9zIChNKS4gRXN0ZSBlbmZvcXVlIG5vcyBwcm9wb3JjaW9uYSB1bmEgbWVkaWRhIGLDoXNpY2EgZGUgcmVuZGltaWVudG8sIGluY2x1eWVuZG8gbcOpdHJpY2FzIGNvbW8gbGEgKmV4YWN0aXR1ZCAoYWNjdXJhY3kpKiB5IGxhIHNlbnNpYmlsaWRhZCBwYXJhIGxhIGNsYXNlIG1heW9yaXRhcmlhLg0KDQpTaSB1biBtb2RlbG8gbcOhcyBhdmFuemFkbyBubyBwdWVkZSBzdXBlcmFyIGVzdGUgYmFzZWxpbmUgZW4gbcOpdHJpY2FzIGNsYXZlLCBjb21vIGVsIMOhcmVhIGJham8gbGEgY3VydmEgUk9DIChBVUMtUk9DKSBvIGxhIHByZWNpc2nDs24gZ2VuZXJhbCwgZW50b25jZXMgZWwgbW9kZWxvIG5vIGVzdGFyw61hIGNhcHR1cmFuZG8gcGF0cm9uZXMgc2lnbmlmaWNhdGl2b3MgZW4gbG9zIGRhdG9zLiBQb3IgbG8gdGFudG8sIG5vIHNlcsOtYSDDunRpbCBwYXJhIGxhIGNsYXNpZmljYWNpw7NuIGRlIGxvcyB0dW1vcmVzIGRlIG1hbWEuDQoNCkVsIFJPQyBtdWVzdHJhIGxhIHRhc2EgZGUgdmVyZGFkZXJvcyBwb3NpdGl2b3MgZnJlbnRlIGEgbGEgdGFzYSBkZSBmYWxzb3MgcG9zaXRpdm9zIGEgZGlmZXJlbnRlcyB1bWJyYWxlcyBkZSBjbGFzaWZpY2FjacOzbiwgbWllbnRyYXMgcXVlIGVsIEFVQyBwcm9wb3JjaW9uYSB1biDDum5pY28gdmFsb3IgcXVlIHJlc3VtZSBlbCByZW5kaW1pZW50byBkZWwgbW9kZWxvIGVuIHTDqXJtaW5vcyBkZSBwcm9iYWJpbGlkYWQgZGUgY2xhc2lmaWNhY2nDs24uIEVzdG9zIHZhbG9yZXMgc2UgdXRpbGl6YXLDoW4gcG9zdGVyaW9ybWVudGUgcGFyYSBjb21wYXJhciBjb24gb3Ryb3MgbW9kZWxvcywgYXl1ZGFuZG8gYSBldmFsdWFyIGN1w6FsIHByb3BvcmNpb25hIGxhcyBtZWpvcmVzIHByZWRpY2Npb25lcyBlbiB0w6lybWlub3MgZGUgc2Vuc2liaWxpZGFkIHkgZXNwZWNpZmljaWRhZC4NCg0KUGFyYSBlbCBiYXNlbGluZSBzZSBpZGVudGlmaWNhIGEgY29udGludWFjacOzbiBsYSBjbGFzZSBtYXlvcml0YXJpYSBlbiBlbCBkYXRhc2V0LiBMdWVnbyBzZSBwcmVkaWNlIGVzdGEgY2xhc2UgZW4gdG9kb3MgbG9zIGNhc29zIHkgc2UgY2FsY3VsYSBsYSBtYXRyaXogZGUgY29uZnVzacOzbiBwYXJhIGV2YWx1YXIgZWwgZGVzZW1wZcOxbyBkZSBsYSBwcmVkaWNjacOzbiBiYXNhZGEgZW4gbGEgY2xhc2UgbWF5b3JpdGFyaWEuDQoNCmBgYHtyfQ0KIyBJZGVudGlmaWNhciBsYSBjbGFzZSBtYXlvcml0YXJpYQ0KbWFqb3JpdHlfY2xhc3MgPC0gYXMuY2hhcmFjdGVyKG5hbWVzKHNvcnQodGFibGUoZGF0YSRkaWFnbm9zaXMpLCBkZWNyZWFzaW5nID0gVFJVRSlbMV0pKQ0KDQojIFByZWRpY2Npw7NuIGRlIGxhIGNsYXNlIG1heW9yaXRhcmlhIGVuIHRvZG9zIGxvcyBjYXNvcw0KbWFqb3JpdHlfcHJlZGljdGlvbnMgPC0gZmFjdG9yKHJlcChtYWpvcml0eV9jbGFzcywgbnJvdyhkYXRhKSksIGxldmVscyA9IGxldmVscyhkYXRhJGRpYWdub3NpcykpDQoNCmNvbmZfbWF0cml4X21ham9yaXR5IDwtIGNvbmZ1c2lvbk1hdHJpeChtYWpvcml0eV9wcmVkaWN0aW9ucywgZGF0YSRkaWFnbm9zaXMpDQpjYXQoIkJhc2VsaW5lOiBQcmVkaWNjacOzbiBkZSBsYSBjbGFzZSBtYXlvcml0YXJpYVxuIikNCnByaW50KGNvbmZfbWF0cml4X21ham9yaXR5KQ0KYGBgDQoNClNlIGNhbGN1bGEgZWwgdmFsb3IgZGVsIFJPQyAoUmVjZWl2ZXIgT3BlcmF0aW5nIENoYXJhY3RlcmlzdGljKSB5IGVsIEFVQyAow4FyZWEgQmFqbyBsYSBDdXJ2YSkgcGFyYSBldmFsdWFyIGVsIHJlbmRpbWllbnRvIGRlIGxhIHByZWRpY2Npw7NuIGJhc2FkYSBlbiBsYSBjbGFzZSBtYXlvcml0YXJpYS4NCg0KYGBge3Igd2FybmluZz1GQUxTRX0NCg0KDQojIENhbGN1bGFyIGxhIHByb2JhYmlsaWRhZCBkZSBjYWRhIGNsYXNlIGJhc2FkYSBlbiBsYXMgZnJlY3VlbmNpYXMgcmVsYXRpdmFzDQpjbGFzc19wcm9iYWJpbGl0aWVzIDwtIHByb3AudGFibGUodGFibGUoZGF0YSRkaWFnbm9zaXMpKQ0KDQojIENyZWFyIHByb2JhYmlsaWRhZGVzIHBhcmEgZWwgYmFzZWxpbmUNCmJhc2VsaW5lX3Byb2JhYmlsaXRpZXMgPC0gcmVwKGNsYXNzX3Byb2JhYmlsaXRpZXNbbWFqb3JpdHlfY2xhc3NdLCBucm93KGRhdGEpKQ0KDQojIENhbGN1bGFyIGVsIFJPQyB5IGVsIEFVQw0Kcm9jX2N1cnZlIDwtIHJvYyhyZXNwb25zZSA9IGRhdGEkZGlhZ25vc2lzLCANCiAgICAgICAgICAgICAgICAgcHJlZGljdG9yID0gYmFzZWxpbmVfcHJvYmFiaWxpdGllcywgDQogICAgICAgICAgICAgICAgIGxldmVscyA9IHJldihsZXZlbHMoZGF0YSRkaWFnbm9zaXMpKSkNCg0KY2F0KCJWYWxvciBkZWwgQVVDLVJPQyBwYXJhIGVsIGJhc2VsaW5lOiIsIGF1Yyhyb2NfY3VydmUpLCAiXG4iKQ0KDQpgYGANCg0KTG9zIHJlc3VsdGFkb3Mgb2J0ZW5pZG9zIHBhcmEgZWwgYmFzZWxpbmUgYmFzYWRvIGVuIGxhIGNsYXNlIG1heW9yaXRhcmlhIHJldmVsYW4gbGFzIGxpbWl0YWNpb25lcyBkZSBlc3RlIGVuZm9xdWUgc2ltcGxpc3RhLiBFbCBtb2RlbG8gY2xhc2lmaWNhIGNvcnJlY3RhbWVudGUgdG9kb3MgbG9zIGNhc29zIGJlbmlnbm9zIChCKSwgbG9ncmFuZG8gdW5hIHNlbnNpYmlsaWRhZCBwZXJmZWN0YSBkZSAxLjAgcGFyYSBlc3RhIGNsYXNlLiBTaW4gZW1iYXJnbywgc3UgZXNwZWNpZmljaWRhZCBlcyAwLjAsIGxvIHF1ZSBpbmRpY2EgcXVlIG5vIGxvZ3JhIGlkZW50aWZpY2FyIG5pbmfDum4gY2FzbyBtYWxpZ25vIChNKS4NCg0KTGEgKmV4YWN0aXR1ZCogZ2VuZXJhbCBkZWwgbW9kZWxvIGVzIGRlbCA2Mi43NCUsIGxvIHF1ZSBjb2luY2lkZSBjb24gbGEgcHJvcG9yY2nDs24gZGUgbGEgY2xhc2UgbWF5b3JpdGFyaWEgZW4gZWwgY29uanVudG8gZGUgZGF0b3MuIEVzdGUgdmFsb3IgcmVwcmVzZW50YSBlbCAqTm8gSW5mb3JtYXRpb24gUmF0ZSAoTklSKSosIG8gbGEgcHJlY2lzacOzbiBlc3BlcmFkYSBzaSBsYXMgcHJlZGljY2lvbmVzIHNlIGhpY2llcmFuIGRlIG1hbmVyYSBhbGVhdG9yaWEgYmFzw6FuZG9zZSDDum5pY2FtZW50ZSBlbiBsYSBkaXN0cmlidWNpw7NuIGRlIGNsYXNlcy4gQWRlbcOhcywgZWwgdmFsb3IgZGUga2FwcGEgZXMgMCwgcmVmbGVqYW5kbyBxdWUgbGFzIHByZWRpY2Npb25lcyBubyBhcG9ydGFuIGluZm9ybWFjacOzbiBtw6FzIGFsbMOhIGRlbCBhemFyLg0KDQpFbCByZXN1bHRhZG8gbcOhcyBzaWduaWZpY2F0aXZvIGVzIGxhIGluY2FwYWNpZGFkIGRlbCBtb2RlbG8gcGFyYSBpZGVudGlmaWNhciBjb3JyZWN0YW1lbnRlIGxvcyBjYXNvcyBtYWxpZ25vcy4gRXN0byBzZSBldmlkZW5jaWEgcG9yIHVuICpWYWxvciBQcmVkaWN0aXZvIFBvc2l0aXZvIChQUFYpKiBkZSA2Mi43NCUsIHBlcm8gdW4gKlZhbG9yIFByZWRpY3Rpdm8gTmVnYXRpdm8gKE5QVikqIGluZGVmaW5pZG8gKE5hTiksIHlhIHF1ZSBudW5jYSBwcmVkaWNlIGxhIGNsYXNlIE0uIEFkZW3DoXMsIGVsIMOhcmVhIGJham8gbGEgY3VydmEgUk9DIChBVUMtUk9DKSBlcyBkZSAwLjUsIGxvIHF1ZSBpbmRpY2EgcXVlIGVsIG1vZGVsbyBubyBlcyBjYXBheiBkZSBkaXNjcmltaW5hciBlbnRyZSBsYXMgZG9zIGNsYXNlcy4NCg0KRXN0ZSBiYXNlbGluZSBkZW5vdGEgbGEgbmVjZXNpZGFkIGRlIHV0aWxpemFyIG1vZGVsb3MgbcOhcyBhdmFuemFkb3MgcXVlIHNlYW4gY2FwYWNlcyBkZSBjYXB0dXJhciBwYXRyb25lcyBkaXNjcmltaW5hdGl2b3MgZW4gbG9zIGRhdG9zIHBhcmEgbG9ncmFyIHVuIG1lam9yIGVxdWlsaWJyaW8gZW50cmUgc2Vuc2liaWxpZGFkIHkgZXNwZWNpZmljaWRhZC4gRXN0ZSBwdW50byBkZSBwYXJ0aWRhIHNlcnZpcsOhIGNvbW8gcmVmZXJlbmNpYSBtw61uaW1hIHBhcmEgZXZhbHVhciBsYSBlZmljYWNpYSBkZSBsb3MgbW9kZWxvcyBwb3N0ZXJpb3Jlcy4NCg0KIyMjIyAzLjIuMi4gw4FyYm9sIGRlIERlY2lzacOzbiAoQ0FSVCk6DQoNCkVsIMOhcmJvbCBkZSBkZWNpc2nDs24gZXMgdW4gbW9kZWxvIHNpbXBsZSBxdWUgZGl2aWRlIGxvcyBkYXRvcyBlbiBzZWdtZW50b3MgYmFzYWRvcyBlbiByZWdsYXMgZGUgZGVjaXNpw7NuLiBFcyDDunRpbCBwYXJhIGNsYXNpZmljYWNpb25lcyBkb25kZSBsYXMgZGVjaXNpb25lcyBzb24gbMOzZ2ljYXMgeSBmw6FjaWxlcyBkZSBlbnRlbmRlcg0KDQpgYGB7cn0NCg0KIyDDgXJib2wgZGUgRGVjaXNpw7NuIHV0aWxpemFuZG8gSy1mb2xkIGNyb3NzLXZhbGlkYXRpb24NCm1vZGVsX2NhcnQgPC0gdHJhaW4oDQogIGRpYWdub3NpcyB+IC4sICAgICAgICAjIFVzYW1vcyB0b2RhcyBsYXMgdmFyaWFibGVzIHByZWRpY3RvcmFzDQogIGRhdGEgPSBkYXRhLA0KICBtZXRob2QgPSAicnBhcnQiLCAgICAgIyDDgXJib2wgZGUgZGVjaXNpw7NuIChDQVJUKQ0KICB0ckNvbnRyb2wgPSB0cmFpbl9jb250cm9sLCAgIyBDb250cm9sIGRlIHZhbGlkYWNpw7NuIGNydXphZGENCiAgbWV0cmljID0gIlJPQyIgICAgICAgICMgRXZhbHVhciB1dGlsaXphbmRvIEFVQyAow4FyZWEgYmFqbyBsYSBjdXJ2YSBST0MpDQopDQoNCiMgVmVyIGVsIHJlc3VtZW4gZGVsIG1vZGVsbyBlbnRyZW5hZG8NCnByaW50KG1vZGVsX2NhcnQpDQoNCiMgRXh0cmFlciBsYSBpbXBvcnRhbmNpYSBkZSBsYXMgdmFyaWFibGVzDQppbXBvcnRhbmNlX2NhcnQgPC0gdmFySW1wKG1vZGVsX2NhcnQsIHNjYWxlID0gRkFMU0UpDQppbXBvcnRhbmNlX2NhcnRfZGYgPC0gaW1wb3J0YW5jZV9jYXJ0JGltcG9ydGFuY2UNCmltcG9ydGFuY2VfY2FydF9kZiR2YXJpYWJsZSA8LSByb3duYW1lcyhpbXBvcnRhbmNlX2NhcnRfZGYpDQoNCmBgYA0KDQpFbCBwcmltZXIgbW9kZWxvIGFuYWxpemFkbyBlcyBlbCDDgXJib2wgZGUgRGVjaXNpw7NuIChDQVJUKS4gRXN0ZSBtb2RlbG8gdXRpbGl6YSB1biB2YWxvciBkZSBjb21wbGVqaWRhZCBkZSBwb2RhIChjcCkgZGUgMC4wMDQ3LCBxdWUgZnVlIHNlbGVjY2lvbmFkbyBjb21vIGVsIG1lam9yIHBhcsOhbWV0cm8gbWVkaWFudGUgdmFsaWRhY2nDs24gY3J1emFkYS4NCg0KTGEgY3VydmEgUk9DIGRlbCBtb2RlbG8gZXMgZGUgMC45MzYyLCBsbyBxdWUgaW5kaWNhIHVuYSBhbHRhIGNhcGFjaWRhZCBwYXJhIGRpc2NyaW1pbmFyIGVudHJlIGxhcyBkb3MgY2xhc2VzLg0KDQpMYSBzZW5zaWJpbGlkYWQgKGNhcGFjaWRhZCBkZWwgbW9kZWxvIHBhcmEgaWRlbnRpZmljYXIgY29ycmVjdGFtZW50ZSBsYXMgb2JzZXJ2YWNpb25lcyBwb3NpdGl2YXMpIGVzIGRlIDAuOTM4MywgbG8gcXVlIHN1Z2llcmUgcXVlIGVsIG1vZGVsbyBlcyBtdXkgZWZpY2llbnRlIHBhcmEgZGV0ZWN0YXIgbGFzIG9ic2VydmFjaW9uZXMgcG9zaXRpdmFzLCBhdW5xdWUgYWxnbyBtZW5vcyBlZmVjdGl2byBxdWUgb3Ryb3MgbW9kZWxvcyBlbiBjdWFudG8gYSBsYSBlc3BlY2lmaWNpZGFkLiBEZSBoZWNobywgbGEgZXNwZWNpZmljaWRhZCAoY2FwYWNpZGFkIHBhcmEgaWRlbnRpZmljYXIgY29ycmVjdGFtZW50ZSBsYXMgb2JzZXJ2YWNpb25lcyBuZWdhdGl2YXMpIGVzIGRlIDAuODk2MCwgbG8gcXVlIHJlcHJlc2VudGEgdW5hIGxldmUgY2HDrWRhIHJlc3BlY3RvIGEgbGEgc2Vuc2liaWxpZGFkLg0KDQpFc3RlIGRlc2VtcGXDsW8gZXMgc8OzbGlkbyB5IGVxdWlsaWJyYWRvLCBwZXJvIG5vIGVzIGVsIG3DoXMgYWx0byBlbnRyZSBsb3MgbW9kZWxvcyBldmFsdWFkb3MuDQoNCiMjIyMgMy4yLjMuIFJhbmRvbSBGb3Jlc3QNCg0KRWwgUmFuZG9tIEZvcmVzdCBlcyB1biBhbGdvcml0bW8gcXVlIGNvbnN0cnV5ZSBtw7psdGlwbGVzIMOhcmJvbGVzIGRlIGRlY2lzacOzbiB5IHJlYWxpemEgdW5hIHByZWRpY2Npw7NuIGFncmVnYW5kbyBsYXMgcHJlZGljY2lvbmVzIGRlIHRvZG9zIGxvcyDDoXJib2xlcyBpbmRpdmlkdWFsZXMuIEVzIHJvYnVzdG8gYW50ZSBlbCBzb2JyZWFqdXN0ZS4NCg0KYGBge3J9DQptb2RlbF9yZiA8LSB0cmFpbigNCiAgZGlhZ25vc2lzIH4gLiwgICAgICAgICMgVXNhbW9zIHRvZGFzIGxhcyB2YXJpYWJsZXMgcHJlZGljdG9yYXMNCiAgZGF0YSA9IGRhdGEsDQogIG1ldGhvZCA9ICJyZiIsICAgICAgICAjIFJhbmRvbSBGb3Jlc3QNCiAgdHJDb250cm9sID0gdHJhaW5fY29udHJvbCwgICMgQ29udHJvbCBkZSB2YWxpZGFjacOzbiBjcnV6YWRhDQogIG1ldHJpYyA9ICJST0MiICAgICAgICAjIEV2YWx1YXIgdXRpbGl6YW5kbyBBVUMgKMOBcmVhIGJham8gbGEgY3VydmEgUk9DKQ0KKQ0KDQojIFZlciBlbCByZXN1bWVuIGRlbCBtb2RlbG8gZW50cmVuYWRvDQpwcmludChtb2RlbF9yZikNCg0KIyBFeHRyYWVyIGxhIGltcG9ydGFuY2lhIGRlIGxhcyB2YXJpYWJsZXMNCmltcG9ydGFuY2VfcmYgPC0gdmFySW1wKG1vZGVsX3JmLCBzY2FsZSA9IEZBTFNFKQ0KaW1wb3J0YW5jZV9yZl9kZiA8LSBpbXBvcnRhbmNlX3JmJGltcG9ydGFuY2UNCmltcG9ydGFuY2VfcmZfZGYkdmFyaWFibGUgPC0gcm93bmFtZXMoaW1wb3J0YW5jZV9yZl9kZikNCg0KYGBgDQoNClBhcmEgZW50ZW5kZXIgbWVqb3IgbG9zIHJlc3VsdGFkb3MgZGVsIG1vZGVsbywgYWNsYXJhciBxdWUgZWwgcGFyw6FtZXRybyBtdHJ5IGVuIGVsIGNvbnRleHRvIGRlIFJhbmRvbSBGb3Jlc3QgZXMgdW5vIGRlIGxvcyBoaXBlcnBhcsOhbWV0cm9zIGNsYXZlIHF1ZSBzZSB1dGlsaXphIHBhcmEgY29udHJvbGFyIGVsIG7Dum1lcm8gZGUgdmFyaWFibGVzIChjYXJhY3RlcsOtc3RpY2FzKSBxdWUgZWwgbW9kZWxvIGNvbnNpZGVyYSBwYXJhIGRpdmlkaXIgY2FkYSBub2RvIGVuIGNhZGEgw6FyYm9sIGRlbCBib3NxdWUuIEVzcGVjw61maWNhbWVudGUsIG10cnkgZGVmaW5lIGN1w6FudGFzIGNhcmFjdGVyw61zdGljYXMgc2Vyw6FuIGVsZWdpZGFzIGFsZWF0b3JpYW1lbnRlIHBhcmEgY2FkYSBub2RvIGN1YW5kbyBzZSBjb25zdHJ1eWUgdW4gw6FyYm9sIGVuIGVsIFJhbmRvbSBGb3Jlc3QuDQoNClJhbmRvbSBGb3Jlc3QsIG11ZXN0cmEgdW4gZGVzZW1wZcOxbyBkZXN0YWNhYmxlLiBFc3RlIG1vZGVsbyBzZWxlY2Npb27DsyBlbCB2YWxvciBkZSBtdHJ5IChuw7ptZXJvIGRlIHZhcmlhYmxlcyBhbGVhdG9yaWFzIHBhcmEgY2FkYSBkaXZpc2nDs24gZGVsIMOhcmJvbCkgaWd1YWwgYSAyLCBsbyBxdWUgb3B0aW1pemEgbGEgY2FwYWNpZGFkIGRlIGRpc2NyaW1pbmFjacOzbi4gU3UgY3VydmEgUk9DIGFsY2FuemEgdW4gdmFsb3IgaW1wcmVzaW9uYW50ZSBkZSAwLjk5MDgsIGxvIHF1ZSBlcyB1biBpbmRpY2Fkb3IgY2xhcm8gZGUgc3UgY2FwYWNpZGFkIHBhcmEgc2VwYXJhciBsYXMgZG9zIGNsYXNlcyBjb24gZ3JhbiBwcmVjaXNpw7NuLiBBZGVtw6FzLCBsYSBzZW5zaWJpbGlkYWQgZGUgMC45ODA0IG11ZXN0cmEgcXVlIFJhbmRvbSBGb3Jlc3QgdGllbmUgdW5hIGV4Y2VsZW50ZSBjYXBhY2lkYWQgcGFyYSBkZXRlY3RhciBjb3JyZWN0YW1lbnRlIGxhcyBvYnNlcnZhY2lvbmVzIHBvc2l0aXZhcywgeSBsYSBlc3BlY2lmaWNpZGFkIGRlIDAuOTI0MSBpbmRpY2EgcXVlIHRhbWJpw6luIGVzIGVmaWNheiBlbiBpZGVudGlmaWNhciBsYXMgb2JzZXJ2YWNpb25lcyBuZWdhdGl2YXMuIEVzdGUgbW9kZWxvIHNvYnJlc2FsZSBwb3Igc3UgYWx0YSBwcmVjaXNpw7NuIGVuIGFtYm9zIGFzcGVjdG9zLCBsbyBxdWUgbG8gY29udmllcnRlIGVuIHVubyBkZSBsb3MgbW9kZWxvcyBtw6FzIHJvYnVzdG9zIHkgY29uZmlhYmxlcyBwYXJhIGVzdGUgY29uanVudG8gZGUgZGF0b3MuDQoNCiMjIyMgMy4yLjQuIFN1cHBvcnQgVmVjdG9yIE1hY2hpbmUgKFNWTSkNCg0KRWwgU3VwcG9ydCBWZWN0b3IgTWFjaGluZSAoU1ZNKSBlcyB1biBhbGdvcml0bW8gcXVlIGludGVudGEgZW5jb250cmFyIGVsIGhpcGVycGxhbm8gcXVlIG1lam9yIHNlcGFyZSBsYXMgZGlmZXJlbnRlcyBjbGFzZXMgZGUgZGF0b3MuDQoNCmBgYHtyfQ0KDQoNCm1vZGVsX3N2bSA8LSB0cmFpbigNCiAgZGlhZ25vc2lzIH4gLiwgICAgICAgICMgVXNhbW9zIHRvZGFzIGxhcyB2YXJpYWJsZXMgcHJlZGljdG9yYXMNCiAgZGF0YSA9IGRhdGEsDQogIG1ldGhvZCA9ICJzdm1SYWRpYWwiLCAgIyBTdXBwb3J0IFZlY3RvciBNYWNoaW5lIGNvbiBrZXJuZWwgcmFkaWFsDQogIHRyQ29udHJvbCA9IHRyYWluX2NvbnRyb2wsICAjIENvbnRyb2wgZGUgdmFsaWRhY2nDs24gY3J1emFkYQ0KICBtZXRyaWMgPSAiUk9DIiAgICAgICAgIyBFdmFsdWFyIHV0aWxpemFuZG8gQVVDICjDgXJlYSBiYWpvIGxhIGN1cnZhIFJPQykNCikNCg0KIyBWZXIgZWwgcmVzdW1lbiBkZWwgbW9kZWxvIGVudHJlbmFkbw0KcHJpbnQobW9kZWxfc3ZtKQ0KDQojIEV4dHJhZXIgbGEgaW1wb3J0YW5jaWEgZGUgbGFzIHZhcmlhYmxlcw0KaW1wb3J0YW5jZV9zdm0gPC0gdmFySW1wKG1vZGVsX3N2bSwgc2NhbGUgPSBGQUxTRSkNCmltcG9ydGFuY2Vfc3ZtX2RmIDwtIGltcG9ydGFuY2Vfc3ZtJGltcG9ydGFuY2UNCmltcG9ydGFuY2Vfc3ZtX2RmJHZhcmlhYmxlIDwtIHJvd25hbWVzKGltcG9ydGFuY2Vfc3ZtX2RmKQ0KDQpgYGANCg0KRWwgdGVyY2VyIG1vZGVsbyBldmFsdWFkbyBlcyBlbCBkZSBNw6FxdWluYXMgZGUgU29wb3J0ZSBWZWN0b3JpYWwgY29uIG7DumNsZW8gUmFkaWFsIChTVk0pLiBFc3RlIG1vZGVsbywgY29uIHVuIHBhcsOhbWV0cm8gZGUgcmVndWxhcml6YWNpw7NuIPCdkLY9MSB5IHVuIHZhbG9yIGRlIHNpZ21hIGRlIDAuMDQ3NSwgbW9zdHLDsyB1biBkZXNlbXBlw7FvIGV4Y2VsZW50ZSBlbiB0w6lybWlub3MgZGUgbGEgY3VydmEgUk9DLCBhbGNhbnphbmRvIHVuIHZhbG9yIGRlIDAuOTk0OCwgZWwgbcOhcyBhbHRvIGVudHJlIHRvZG9zIGxvcyBtb2RlbG9zLiBFc3RhIG3DqXRyaWNhIHJlZmxlamEgdW5hIGNhcGFjaWRhZCBkZSBkaXNjcmltaW5hY2nDs24gc3VwZXJpb3IsIGxvIHF1ZSBpbXBsaWNhIHF1ZSBlbCBtb2RlbG8gdGllbmUgdW5hIGFsdGEgaGFiaWxpZGFkIHBhcmEgc2VwYXJhciBjb3JyZWN0YW1lbnRlIGxhcyBjbGFzZXMgJ05lZ2F0aXZlJyB5ICdQb3NpdGl2ZScuIExhIHNlbnNpYmlsaWRhZCBkZSAwLjk3NDggeSBsYSBlc3BlY2lmaWNpZGFkIGRlIDAuOTY3MCB0YW1iacOpbiBzb24gbm90YWJsZW1lbnRlIGFsdGFzLCBsbyBxdWUgc3VnaWVyZSBxdWUgZWwgbW9kZWxvIHRpZW5lIHVuIGJ1ZW4gcmVuZGltaWVudG8gdGFudG8gZW4gbGEgZGV0ZWNjacOzbiBkZSBsYXMgb2JzZXJ2YWNpb25lcyBwb3NpdGl2YXMgY29tbyBlbiBsYSBjb3JyZWN0YSBpZGVudGlmaWNhY2nDs24gZGUgbGFzIG5lZ2F0aXZhcy4gU2luIGVtYmFyZ28sIGVzIGltcG9ydGFudGUgZGVzdGFjYXIgcXVlIGVsIG1vZGVsbyBTVk0gcHJlc2VudMOzIHZhcmlhcyBhZHZlcnRlbmNpYXMgZHVyYW50ZSBlbCBwcm9jZXNvIGRlIG9wdGltaXphY2nDs24gKHdhcm5pbmdzKSwgcmVsYWNpb25hZGFzIGNvbiBwcm9ibGVtYXMgZGUgY29udmVyZ2VuY2lhIHkgcHJvYmFiaWxpZGFkZXMgZXh0cmVtYXMgZGUgMCBvIDEuIEVzdG8gcG9kcsOtYSBpbmRpY2FyIHF1ZSBlbCBtb2RlbG8gcG9kcsOtYSBlc3RhciBzb2JyZWFqdXN0YW5kbyBvIGVuZnJlbnRhbmRvIGRpZmljdWx0YWRlcyBwYXJhIGVuY29udHJhciB1biBlcXVpbGlicmlvIGVzdGFibGUsIGxvIHF1ZSBkZWJlIHRlbmVyc2UgZW4gY3VlbnRhIGFsIGV2YWx1YXIgc3UgZXN0YWJpbGlkYWQgeSBnZW5lcmFsaXphY2nDs24uDQoNCiMjIyMgMy4yLjUuIFJlZ3Jlc2nDs24gTG9nw61zdGljYQ0KDQpFc3RlIGFsZ29yaXRtbyBlcyB1biBtb2RlbG8gZGUgY2xhc2lmaWNhY2nDs24gcXVlIHByZWRpY2UgbGEgcHJvYmFiaWxpZGFkIGRlIHF1ZSB1bmEgb2JzZXJ2YWNpw7NuIHBlcnRlbnpjYSBhIHVuYSBjbGFzZSBvIG5vLg0KDQpgYGB7ciB3YXJuaW5nPUZBTFNFfQ0KDQoNCm1vZGVsX2xvZ2l0IDwtIHRyYWluKA0KICBkaWFnbm9zaXMgfiAuLCAgICAgICAgIyBVc2Ftb3MgdG9kYXMgbGFzIHZhcmlhYmxlcyBwcmVkaWN0b3Jhcw0KICBkYXRhID0gZGF0YSwNCiAgbWV0aG9kID0gImdsbSIsICAgICAgICMgUmVncmVzacOzbiBMb2fDrXN0aWNhDQogIHRyQ29udHJvbCA9IHRyYWluX2NvbnRyb2wsICAjIENvbnRyb2wgZGUgdmFsaWRhY2nDs24gY3J1emFkYQ0KICBtZXRyaWMgPSAiUk9DIiAgICAgICAgIyBFdmFsdWFyIHV0aWxpemFuZG8gQVVDICjDgXJlYSBiYWpvIGxhIGN1cnZhIFJPQykNCikNCg0KIyBWZXIgZWwgcmVzdW1lbiBkZWwgbW9kZWxvIGVudHJlbmFkbw0KcHJpbnQobW9kZWxfbG9naXQpDQoNCiMgRXh0cmFlciBsYSBpbXBvcnRhbmNpYSBkZSBsYXMgdmFyaWFibGVzDQppbXBvcnRhbmNlX2xvZ2l0IDwtIHZhckltcChtb2RlbF9sb2dpdCwgc2NhbGUgPSBGQUxTRSkNCmltcG9ydGFuY2VfbG9naXRfZGYgPC0gaW1wb3J0YW5jZV9sb2dpdCRpbXBvcnRhbmNlDQppbXBvcnRhbmNlX2xvZ2l0X2RmJHZhcmlhYmxlIDwtIHJvd25hbWVzKGltcG9ydGFuY2VfbG9naXRfZGYpDQoNCmBgYA0KDQpFbCDDumx0aW1vIG1vZGVsbyBjb25zaWRlcmFkbyBlcyBlbCBNb2RlbG8gZGUgcmVncmVzacOzbiBMb2fDrXN0aWNhLiBFc3RlIG1vZGVsbywgYSBwZXNhciBkZSBzZXIgc2VuY2lsbG8gZW4gc3UgZXN0cnVjdHVyYSwgbW9zdHLDsyB1biByZW5kaW1pZW50byByZXNwZXRhYmxlLiBTdSBjdXJ2YSBST0MgYWxjYW56w7MgdW4gdmFsb3IgZGUgMC45NTUyLCBsbyBjdWFsIGVzIGluZmVyaW9yIGEgbG9zIGRlIFJhbmRvbSBGb3Jlc3QgeSBTVk0sIHBlcm8gc2lndWUgc2llbmRvIGFkZWN1YWRvIHBhcmEgdGFyZWFzIGRlIGNsYXNpZmljYWNpw7NuLiBMYSBzZW5zaWJpbGlkYWQgZGUgMC45NDM4IGluZGljYSBxdWUgZWwgbW9kZWxvIHRpZW5lIHVuYSBidWVuYSBjYXBhY2lkYWQgcGFyYSBpZGVudGlmaWNhciBsYXMgb2JzZXJ2YWNpb25lcyBwb3NpdGl2YXMsIG1pZW50cmFzIHF1ZSBsYSBlc3BlY2lmaWNpZGFkIGRlIDAuOTQ4NCBlcyBsaWdlcmFtZW50ZSBtZWpvciBxdWUgbGEgc2Vuc2liaWxpZGFkLCBsbyBxdWUgc3VnaWVyZSBxdWUgZWwgbW9kZWxvIHRpZW5lIHVuIGRlc2VtcGXDsW8gbGlnZXJhbWVudGUgbWVqb3IgcGFyYSBkZXRlY3RhciBsYXMgb2JzZXJ2YWNpb25lcyBuZWdhdGl2YXMgZW4gY29tcGFyYWNpw7NuIGNvbiBsYXMgcG9zaXRpdmFzLiBBdW5xdWUgZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gTG9nw61zdGljYSBlcyBmdW5jaW9uYWwsIHN1IHJlbmRpbWllbnRvIGVuIHTDqXJtaW5vcyBkZSBsYSBjdXJ2YSBST0MgZXMgYWxnbyBpbmZlcmlvciBlbiBjb21wYXJhY2nDs24gY29uIGxvcyBtb2RlbG9zIG3DoXMgY29tcGxlam9zIGNvbW8gU1ZNIHkgUmFuZG9tIEZvcmVzdC4NCg0KIyMjIyAzLjIuNi4gUmVkIE5ldXJvbmFsDQoNCkxhcyByZWRlcyBuZXVyb25hbGVzIHNvbiB1biBtb2RlbG8gZGUgYXByZW5kaXphamUgcHJvZnVuZG8gcXVlIGltaXRhIGVsIGZ1bmNpb25hbWllbnRvIGRlbCBjZXJlYnJvIGh1bWFuby4gRXN0w6FuIGNvbXB1ZXN0YXMgcG9yIGNhcGFzIGRlIG5ldXJvbmFzIGludGVyY29uZWN0YWRhcyBxdWUgcHJvY2VzYW4gbGEgaW5mb3JtYWNpw7NuIHkgYXByZW5kZW4gYSBwYXJ0aXIgZGUgbG9zIGRhdG9zLiBQdWVkZSBzZXIgaW50ZXJlc2FudGUgZXZhbHVhciBlbCByZW5kaW1pZW50byBkZSB1bmEgcmVkIG5ldXJvbmFsIGVuIGNvbXBhcmFjacOzbiBjb24gbG9zIG1vZGVsb3MgdHJhZGljaW9uYWxlcyBkZSBhcHJlbmRpemFqZSBzdXBlcnZpc2Fkby4NCg0KYGBge3Igd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0NCiMgRW50cmVuYXIgdW5hIHJlZCBuZXVyb25hbCBwYXJhIGNsYXNpZmljYWNpw7NuIGJpbmFyaWENCm1vZGVsX25uIDwtIHRyYWluKA0KICBkaWFnbm9zaXMgfiAuLCAgICAgICAgIyBVc2Ftb3MgdG9kYXMgbGFzIHZhcmlhYmxlcyBwcmVkaWN0b3Jhcw0KICBkYXRhID0gZGF0YSwNCiAgbWV0aG9kID0gIm5uZXQiLCAgICAgICMgUmVkIE5ldXJvbmFsDQogIHRyQ29udHJvbCA9IHRyYWluX2NvbnRyb2wsICAjIENvbnRyb2wgZGUgdmFsaWRhY2nDs24gY3J1emFkYQ0KICBtZXRyaWMgPSAiUk9DIiwgICAgICAgIyBFdmFsdWFyIHV0aWxpemFuZG8gQVVDICjDgXJlYSBiYWpvIGxhIGN1cnZhIFJPQykNCiAgdmVyYm9zZSA9IEZBTFNFICAgICAgICMgU3VwcmltaXIgbG9zIGRldGFsbGVzIGRlbCBlbnRyZW5hbWllbnRvDQopDQpgYGANCg0KYGBge3J9DQoNCiMgSW1wcmltaXIgbG9zIHJlc3VsdGFkb3MgZGVsIG1vZGVsbw0KcHJpbnQobW9kZWxfbm4kcmVzdWx0cykNCg0KDQojIEV4dHJhZXIgbGEgaW1wb3J0YW5jaWEgZGUgbGFzIHZhcmlhYmxlcw0KaW1wb3J0YW5jZV9ubiA8LSB2YXJJbXAobW9kZWxfbm4sIHNjYWxlID0gRkFMU0UpDQppbXBvcnRhbmNlX25uX2RmIDwtIGltcG9ydGFuY2Vfbm4kaW1wb3J0YW5jZQ0KaW1wb3J0YW5jZV9ubl9kZiR2YXJpYWJsZSA8LSByb3duYW1lcyhpbXBvcnRhbmNlX25uX2RmKQ0KDQoNCmBgYA0KDQpFbCBtb2RlbG8gZGUgUmVkIE5ldXJvbmFsLCB0aWVuZSB1biByZW5kaW1pZW50byBtdXkgc2ltaWxhciBhbCBkZSBSYW5kb20gRm9yZXN0IHkgU1ZNLCBjb24gdW5hIGN1cnZhIFJPQyBkZSAwLjk5MjIsIHVuYSBzZW5zaWJpbGlkYWQgZGUgMC45NTUxIHkgdW5hIGVzcGVjaWZpY2lkYWQgZGUgMC45NDMxLiBFc3RvIGluZGljYSBxdWUgbGEgcmVkIG5ldXJvbmFsIGVzIGNhcGF6IGRlIGRpc2NyaW1pbmFyIGVmaWNhem1lbnRlIGVudHJlIGxhcyBjbGFzZXMgJ0JlbmluZ25vJyB5ICdNYWxpZ25vJywgY29uIHVuYSBhbHRhIGNhcGFjaWRhZCBwYXJhIGRldGVjdGFyIGxhcyBvYnNlcnZhY2lvbmVzIHBvc2l0aXZhcyB5IG5lZ2F0aXZhcy4gQXVucXVlIGxhIHJlZCBuZXVyb25hbCBlcyB1biBtb2RlbG8gbcOhcyBjb21wbGVqbyB5IHJlcXVpZXJlIG3DoXMgdGllbXBvIGRlIGVudHJlbmFtaWVudG8sIHN1IHJlbmRpbWllbnRvIGVzIGNvbXBhcmFibGUgYWwgZGUgbG9zIG1vZGVsb3MgbcOhcyB0cmFkaWNpb25hbGVzLCBsbyBxdWUgc3VnaWVyZSBxdWUgcHVlZGUgc2VyIHVuYSBvcGNpw7NuIHZpYWJsZSBwYXJhIGxhIGNsYXNpZmljYWNpw7NuIGRlIHR1bW9yZXMgZGUgbWFtYS4NCg0KIyMjIyAzLjIuNyBFdmFsdWFjacOzbiBkZSBsb3MgbW9kZWxvcw0KDQpFc3RhIHNlY2Npw7NuIHNlIGRlZGljYSBhIGV2YWx1YXIgZWwgcmVuZGltaWVudG8gZGUgbG9zIG1vZGVsb3MgdXRpbGl6YWRvcyBlbiBlbCBhbsOhbGlzaXMuIExhIGNvbXBhcmFjacOzbiBkZSBtb2RlbG9zIHNlIHJlYWxpemEgdXRpbGl6YW5kbyB0w6ljbmljYXMgZGUgdmFsaWRhY2nDs24gY3J1emFkYSwgY29tbyBLLWZvbGQsIHBhcmEgb2J0ZW5lciB1bmEgdmlzacOzbiByb2J1c3RhIGRlbCBkZXNlbXBlw7FvIGRlIGNhZGEgdW5vLiBMb3MgcmVzdWx0YWRvcyBvYnRlbmlkb3MgZGUgZXN0b3MgbW9kZWxvcyBzZSBjb21wYXJhbiBhIHRyYXbDqXMgZGVsIHJlc3VtZW4gZGUgcmVzYW1wbGluZyB5IHNlIHZpc3VhbGl6YW4gbWVkaWFudGUgZWwgZGlhZ3JhbWEgYndwbG90IHBhcmEgaWRlbnRpZmljYXIgY3XDoWwgbW9kZWxvIG9mcmVjZSBtZWpvciBwcmVjaXNpw7NuIHkgZ2VuZXJhbGl6YWNpw7NuIGVuIGxhcyBwcmVkaWNjaW9uZXMuDQoNCmBgYHtyfQ0KcmVzYW1wbGVzIDwtIHJlc2FtcGxlcyhsaXN0KGNhcnQgPSBtb2RlbF9jYXJ0LCByZiA9IG1vZGVsX3JmLCBzdm0gPSBtb2RlbF9zdm0sIGxvZ2l0ID0gbW9kZWxfbG9naXQsIG5uID0gbW9kZWxfbm4pKQ0KDQpzdW1tYXJ5KHJlc2FtcGxlcykNCmJ3cGxvdChyZXNhbXBsZXMpDQoNCmBgYA0KDQpTZSBvYnNlcnZhIHF1ZSBlbCBtZWpvciBtb2RlbG8gZXMgZWwgIlNWTSIgeWEgcXVlIHRpZW5lIGVsIHZhbG9yIG3DoXMgYWx0byBkZSBBVUMtUk9DLCBhZGVtw6FzIGRlIHRlbmVyIHVuYSBhbHRhIHNlbnNpYmlsaWRhZCB5IGVzcGVjaWZpY2lkYWQuIEVsIG1vZGVsbyAiUmFuZG9tIEZvcmVzdCIgdGFtYmnDqW4gdGllbmUgdW4gYnVlbiByZW5kaW1pZW50bywgcGVybyBsYSBlc3BlY2lmaWNpZGFkIG5vIGVzIHRhbiBhbHRhIHByb2JhYmxlbWVudGUgcG9yIGVsIG91dGxpZXIgcXVlIHNlIG9ic2VydmEgZW4gZWwgZGlhZ3JhbWEgYndwbG90Lg0KDQpSZWRlcyBuZXVyb25hbGVzIHRhbWJpZW4gY3VlbnRhIGNvbiBidWVuIGRlc2VtcGXDsW8gYXVucXVlIGNvbiBtZW5vciBzZW5zaWJpbGlkYWQgeSBlc3BlY2lmaWNpZGFkIHF1ZSBsb3MgbW9kZWxvcyBhbnRlcmlvcmVzLiBMb2dpdCB5IENBUlQgcHJlc2VudGFuIHVuIHJlbmRpbWllbnRvIGluZmVyaW9yIGVuIGNvbXBhcmFjacOzbiBjb24gbG9zIG90cm9zIG1vZGVsb3MuDQoNCiMjIyAzLjMuIEV4cGVyaW1lbnRhY2nDs24gZGUgbW9kZWxvcyBzdXBlcnZpc2Fkb3MgY29uIHJlZHVjY2nDs24gZGUgdmFyaWFibGVzDQoNCkVuIGVzdGEgc2VjY2nDs24sIHNlIGV4cGVyaW1lbnRhcsOhIGNvbiBsYSByZWR1Y2Npw7NuIGRlIHZhcmlhYmxlcyBwYXJhIGV2YWx1YXIgc2kgZXMgcG9zaWJsZSBtZWpvcmFyIGVsIHJlbmRpbWllbnRvIGRlIGxvcyBtb2RlbG9zIGRlIGFwcmVuZGl6YWplIHN1cGVydmlzYWRvLiBMYSByZWR1Y2Npw7NuIGRlIHZhcmlhYmxlcyBpbXBsaWNhIHNlbGVjY2lvbmFyIHVuIHN1YmNvbmp1bnRvIGRlIGNhcmFjdGVyw61zdGljYXMgbcOhcyByZWxldmFudGVzIHkgZWxpbWluYXIgbGFzIG1lbm9zIGltcG9ydGFudGVzLCBsbyBxdWUgcHVlZGUgc2ltcGxpZmljYXIgZWwgbW9kZWxvIHkgbWVqb3JhciBzdSBjYXBhY2lkYWQgcHJlZGljdGl2YS4gDQoNCg0KU2UgcHJvY2VkZXLDoSBhIHJlYWxpemFyIHVuIGVzdHVkaW8gZGUgbGEgaW1wb3J0YW5jaWEgZGUgbGFzIHZhcmlhYmxlcyBwYXJhIGlkZW50aWZpY2FyIGN1w6FsZXMgc29uIGxhcyBjYXJhY3RlcsOtc3RpY2FzIG3DoXMgcmVsZXZhbnRlcyBlbiBsYSBwcmVkaWNjacOzbiBkZWwgZGlhZ27Ds3N0aWNvIGRlIGPDoW5jZXIgZGUgbWFtYSB5IGFzw60gcG9kZXIgc2VsZWNjaW9uYXIgbGFzIG3DoXMgaW1wb3J0YW50ZXMgcGFyYSBtZWpvcmFyIGVsIHJlbmRpbWllbnRvIGRlIGxvcyBtb2RlbG9zLg0KDQojIyMjIDMuMy4xLiBFc3R1ZGlvIGRlIGxhIEltcG9ydGFuY2lhIGRlIGxhcyBWYXJpYWJsZXMNCg0KRWwgZXN0dWRpbyBkZSBsYSBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMgbm9zIHBlcm1pdGUgaWRlbnRpZmljYXIgY3XDoWxlcyBzb24gbGFzIGNhcmFjdGVyw61zdGljYXMgcXVlIG3DoXMgaW5mbHV5ZW4gZW4gbGFzIHByZWRpY2Npb25lcyByZWFsaXphZGFzLg0KDQpFbCBvYmpldGl2byBkZWwgZXN0dWRpbyBkZSBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMgZXMgZWxpbWluYXIgYXF1ZWxsYXMgcXVlIGNvbnNpc3RlbnRlbWVudGUgdGllbmVuIGJhamEgaW1wb3J0YW5jaWEgZW4gdG9kb3MgbG9zIG1vZGVsb3M6DQoNCjEtIE9idGVuZXIgbGEgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzIGRlIGNhZGEgbW9kZWxvLiAyLSBOb3JtYWxpemFyIGxhIGltcG9ydGFuY2lhIHBhcmEgY29tcGFyYXJsYSBlbnRyZSBtb2RlbG9zLiAzLSBDYWxjdWxhciB1bmEgbcOpdHJpY2EgY29uc29saWRhZGEgZGUgaW1wb3J0YW5jaWEgcHJvbWVkaW8uIDQtIElkZW50aWZpY2FyIGxhcyB2YXJpYWJsZXMgY29uIG1lbm9yIGltcGFjdG8gZW4gdG9kb3MgbG9zIG1vZGVsb3MuDQoNCiMjIyMjIDMuMy4xLjEuIMOBcmJvbCBkZSBEZWNpc2nDs24gKENBUlQpDQoNCkVuIGxvcyDDoXJib2xlcyBkZSBkZWNpc2nDs24sIGxhIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcyBzZSBjYWxjdWxhIGVuIGZ1bmNpw7NuIGRlIGxhcyBnYW5hbmNpYXMgZGUgcmVkdWNjacOzbiBkZSBpbXB1cmV6YSwgcG9yIGVqZW1wbG8sIGxhIHJlZHVjY2nDs24gZGUgbGEgZW50cm9ww61hIG8gZGVsIMOtbmRpY2UgR2luaSBlbiBsb3Mgbm9kb3MgZG9uZGUgbGEgdmFyaWFibGVzIGVzIHV0aWxpemFkYSBwYXJhIGRpdmlkciBsb3MgZGF0b3MuDQoNCmBgYHtyfQ0KcGxvdChpbXBvcnRhbmNlX2NhcnQsIG1haW4gPSAiSW1wb3J0YW5jaWEgZGUgVmFyaWFibGVzIC0gQ0FSVCIpDQpgYGANCg0KTGFzIHZhcmlhYmxlcyBjb24gbWF5b3IgaW1wb3J0YW5jaWEgZW4gZWwgbW9kZWxvIENBUlQgc29uICJjb25jYXZlLnBvaW50c193b3JzdCIsICJjb25jYXZlLnBvaW50c19tZWFuIiwgInJhZGl1c193b3JzdCIsICJhcmVhX3dvcnN0IiB5ICJwZXJpbWV0ZXJfd29yc3QiLiBFc3RhcyB2YXJpYWJsZXMgc29uIGxhcyBtw6FzIGluZmx1eWVudGVzIGVuIGxhIHByZWRpY2Npw7NuIGRlbCBkaWFnbsOzc3RpY28gZGUgY8OhbmNlciBkZSBtYW1hLCBsbyBxdWUgc3VnaWVyZSBxdWUgbGFzIGNhcmFjdGVyw61zdGljYXMgcmVsYWNpb25hZGFzIGNvbiBsb3MgcHVudG9zIGPDs25jYXZvcywgZWwgcmFkaW8sIGVsIMOhcmVhIHkgZWwgcGVyw61tZXRybyBkZWwgdHVtb3Igc29uIGNyw610aWNhcyBwYXJhIGRpc3Rpbmd1aXIgZW50cmUgdHVtb3JlcyBtYWxpZ25vcyB5IGJlbmlnbm9zLg0KDQojIyMjIyAzLjMuMS4yLiBSYW5kb20gRm9yZXN0DQoNCkVuIFJhbmRvbSBGb3Jlc3QgbGEgaW1wb3J0YW5jaWEgc2UgY2FsY3VsYSBtZWRpYW50ZSBkb3MgZW5mb3F1ZXMgY29tdW5lczoNCg0KLSAgIEltcG9ydGFuY2lhIGJhc2FkYSBlbiBwZXJtdXRhY2nDs246IEV2YWzDumEgY8OzbW8gY2FtYmlhIGxhIHByZWNpc2nDs24gZGVsIG1vZGVsbyBhbCBwZXJtdXRhciBhbGVhdG9yaWFtZW50ZSBsb3MgdmFsb3JlcyBkZSB1bmEgdmFyaWFibGUuDQotICAgUmVkdWNjacOzbiBwcm9tZWRpbyBkZSBsYSBpbXB1cmV6YTogQ2FsY3VsYSBjdcOhbnRvIGNvbnRyaWJ1eWUgdW5hIHZhcmlhYmxlIGEgbGEgcmVkdWNjacOzbiBkZSBpbXB1cmV6YSBhIHRyYXbDqXMgZGUgdG9kb3MgbG9zIMOhcmJvbGVzIGRlbCBib3NxdWUuDQoNCmBgYHtyfQ0KcGxvdChpbXBvcnRhbmNlX3JmLCBtYWluID0gIkltcG9ydGFuY2lhIGRlIFZhcmlhYmxlcyAtIFJhbmRvbSBGb3Jlc3QiKQ0KYGBgDQoNCkVuIGVzdGUgY2FzbyBsYXMgdmFyaWFibGVzIG3DoXMgaW1wb3J0YW50ZXMgc29uIHNpbWlsYXJlcyBhbCBtb2RlbG8gYW50ZXJpb3IuDQoNCiMjIyMjIDMuMy4xLjMuIFN1cHBvcnQgVmVjdG9yIE1hY2hpbmUgKFNWTSkNCg0KRWwgY2FsY3VsbyBkZSBsYSBpbXBvcnRhbmNpYSBlbiBTVk0gbm8gZXMgdGFuIGRpcmVjdG8sIHlhIHF1ZSBlc3RlIG1vZGVsbyBubyBzZSBiYXNhIGVuIHVuYSBlc3RydWN0dXJhIGplcsOhcnF1aWNhIG8gZW4gdW5hIGdyZWdhY2nDs24gZGUgw6FyYm9sZXMuIFBvZGVtb3MgZXN0aW1hciBsYSBpbXBvcnRhbmNpYSBkZSBsYXMgdmFyaWFibGVzIG1lZGlhbnRlIGFuw6FsaXNpcyBwb3N0LWhvYywgY29tbyBsYSBldmFsdWFjacOzbiBkZSBsb3MgY29lZmljaWVudGVzIGVuIGVsIGVzcGFjaW8gZm9ybWFkbyBwb3IgZWwgbsO6Y2xlbyByYWRpYWwuDQoNCmBgYHtyfQ0KcGxvdChpbXBvcnRhbmNlX3N2bSwgbWFpbiA9ICJJbXBvcnRhbmNpYSBkZSBWYXJpYWJsZXMtIFNWTSIpDQpgYGANCg0KQWwgaWd1YWwgcXVlIGVuIGxvcyBtb2RlbG9zIGFudGVyaW9yZXMsIHBhcmVjZSBxdWUgbGFzIHZhcmlhYmxlcyBkZSBwZW9yIHkgbWVkaWEgZGUgcGVyaW1ldHJvIHJhZGlvIGFyZWEgeSBjb25jYXZpZGFkIHNvbiBsYXMgbcOhcyBpbXBvcnRhbnRlcyBlbiBsYSBwcmVkaWNjacOzbiBkZWwgZGlhZ27Ds3N0aWNvIGRlIGPDoW5jZXIgZGUgbWFtYS4NCg0KIyMjIyMgMy4zLjEuNC4gUmVncmVzacOzbiBMb2fDrXN0aWNhDQoNCkVuIHJlZ3Jlc2nDs24gbG9nw61zdGljYSwgbGEgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzIHNlIHB1ZWRlIGFuYWxpemFyIG1lZGlhbnRlIGxvcyBjb2VmaWNpZW50ZXMgZXN0aW1hZG9zIGRlbCBtb2RlbG8uIEVzdG9zIGNvZWZpY2llbnRlcyBpbmRpY2FuIGxhIG1hZ25pdHVkIHkgbGEgZGlyZWNjacOzbiBkZWwgZWZlY3RvIGRlIGNhZGEgdmFyaWFibGUgZW4gbGEgcHJvYmFiaWxpZGFkIGRlIHF1ZSB1biB0dW1vciBzZWEgbWFsaWduby4NCg0KYGBge3J9DQpwbG90KGltcG9ydGFuY2VfbG9naXQsIG1haW4gPSAiSW1wb3J0YW5jaWEgZGUgVmFyaWFibGVzIC0gUmVncmVzacOzbiBMb2fDrXN0aWNhIikNCmBgYA0KDQpFbiBlc3RlIGNhc28gbGEgc2ltZXRyw61hIHRvbWEgdW4gcGFwZWwgaW1wb3J0YW50ZSBlbiBsYSBwcmVkaWNjacOzbiBkZWwgZGlhZ27Ds3N0aWNvIGRlIGPDoW5jZXIgZGUgbWFtYS4NCg0KIyMjIyMgMy4zLjEuNSBSZWQgTmV1cm9uYWwNCg0KRW4gbGFzIHJlZGVzIG5ldXJvbmFsZXMsIGxhIGltcG9ydGFuY2lhIGRlIGxhcyB2YXJpYWJsZXMgcHVlZGUgc2VyIG3DoXMgZGlmw61jaWwgZGUgaW50ZXJwcmV0YXIgZGViaWRvIGEgbGEgY29tcGxlamlkYWQgZGVsIG1vZGVsby4gU2luIGVtYmFyZ28sIGVzIHBvc2libGUgYW5hbGl6YXIgbGEgY29udHJpYnVjacOzbiBkZSBjYWRhIHZhcmlhYmxlIGEgbGEgc2FsaWRhIGRlIGxhIHJlZCBtZWRpYW50ZSB0w6ljbmljYXMgZGUgYmFja3Byb3BhZ2F0aW9uIHkgYW7DoWxpc2lzIGRlIHNlbnNpYmlsaWRhZC4NCg0KYGBge3J9DQpwbG90KGltcG9ydGFuY2Vfbm4sIG1haW4gPSAiSW1wb3J0YW5jaWEgZGUgVmFyaWFibGVzIC0gUmVkIE5ldXJvbmFsIikNCmBgYA0KDQpBbCBpZ3VhbCBxdWUgZW4gbG9zIG1vZGVsb3MgYW50ZXJpb3JlcywgbGFzIHZhcmlhYmxlcyByZWxhY2lvbmFkYXMgY29uIGVsIHRhbWHDsW8geSBsYSBmb3JtYSBkZWwgdHVtb3IsIGNvbW8gZWwgcmFkaW8sIGVsIMOhcmVhIHkgZWwgcGVyw61tZXRybywgcGFyZWNlbiBzZXIgbGFzIG3DoXMgaW5mbHV5ZW50ZXMgZW4gbGEgcHJlZGljY2nDs24gZGVsIGRpYWduw7NzdGljbyBkZSBjw6FuY2VyIGRlIG1hbWEuDQoNCiMjIyMgMy4zLjIgTm9ybWFsaXphY2nDs24gZGUgVmFyaWFibGVzDQoNClBhcmEgKipub3JtYWxpemFyIGxhIGltcG9ydGFuY2lhKiogeSBwb2RlciBjb21wYXJhcmxhIGVudHJlIG1vZGVsb3MsIHNlIGRlYmUgY2FsY3VsYXIgbGEgKippbXBvcnRhbmNpYSByZWxhdGl2YSoqIGRlIGNhZGEgdmFyaWFibGUgZW4gdMOpcm1pbm9zIGRlIGNvbnRyaWJ1Y2nDs24gYSBsYSBwcmVkaWNjacOzbiBkZWwgbW9kZWxvLiBFc3RvIHBlcm1pdGUgKiplc3RhbmRhcml6YXIgbGFzIG1lZGljaW9uZXMqKiB5IGhhY2VybGFzIGNvbXBhcmFibGVzLCBpbmRlcGVuZGllbnRlbWVudGUgZGVsIG1vZGVsbyBvIGRlbCBhbGdvcml0bW8gdXNhZG8uIEx1ZWdvLCBzZSBjYWxjdWxhIHVuYSAqKm3DqXRyaWNhIGNvbnNvbGlkYWRhKiogcXVlIHJlcHJlc2VudGUgbGEgKippbXBvcnRhbmNpYSBwcm9tZWRpbyoqIGRlIGxhcyB2YXJpYWJsZXMgZW4gdG9kb3MgbG9zIG1vZGVsb3MuIEVzdGUgdmFsb3IgY29uc29saWRhZG8gc2Ugb2J0aWVuZSBhbCBwcm9tZWRpYXIgbGFzICoqaW1wb3J0YW5jaWFzIGluZGl2aWR1YWxlcyoqIGRlIGNhZGEgdmFyaWFibGUgZW50cmUgbG9zIGRpZmVyZW50ZXMgbW9kZWxvcy4gUG9yIMO6bHRpbW8sIHNlIGlkZW50aWZpY2FuIGxhcyAqKnZhcmlhYmxlcyBjb24gbWVub3IgaW1wYWN0byoqIGVuIHRvZG9zIGxvcyBtb2RlbG9zIGFsIGFuYWxpemFyIGFxdWVsbGFzIHF1ZSB0aWVuZW4gdW5hIGltcG9ydGFuY2lhICoqc2lnbmlmaWNhdGl2YW1lbnRlIGJhamEqKiBlbiBjb21wYXJhY2nDs24gY29uIG90cmFzLg0KDQpgYGB7cn0NCiMgQ29uc29saWRhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMNCmltcG9ydGFuY2VfY29tYmluZWQgPC0gbWVyZ2UoDQogIG1lcmdlKGltcG9ydGFuY2VfY2FydF9kZiwgaW1wb3J0YW5jZV9yZl9kZiwgYnkgPSAidmFyaWFibGUiLCBzdWZmaXhlcyA9IGMoIl9jYXJ0IiwgIl9yZiIpKSwNCiAgbWVyZ2UoaW1wb3J0YW5jZV9zdm1fZGYsIGltcG9ydGFuY2VfbG9naXRfZGYsIGJ5ID0gInZhcmlhYmxlIiwgc3VmZml4ZXMgPSBjKCJfc3ZtIiwgIl9sb2dpdCIpKSwNCiAgDQogIGJ5ID0gInZhcmlhYmxlIg0KKQ0KDQppbXBvcnRhbmNlX2NvbWJpbmVkIDwtIG1lcmdlKGltcG9ydGFuY2VfY29tYmluZWQsIGltcG9ydGFuY2Vfbm5fZGYsIGJ5ID0gInZhcmlhYmxlIikNCg0KIyBQcm9tZWRpbyBkZSBpbXBvcnRhbmNpYQ0KaW1wb3J0YW5jZV9jb21iaW5lZCRtZWFuX2ltcG9ydGFuY2UgPC0gcm93TWVhbnMoaW1wb3J0YW5jZV9jb21iaW5lZFssIC0xXSwgbmEucm0gPSBUUlVFKQ0KDQojIFNlbGVjY2lvbmFyIGxhcyBtZW5vcyBpbXBvcnRhbnRlcyAocG9yIGRlYmFqbyBkZSB1biB1bWJyYWwsIHBvciBlamVtcGxvLCBlbCBwZXJjZW50aWwgOTApDQp0aHJlc2hvbGQgPC0gcXVhbnRpbGUoaW1wb3J0YW5jZV9jb21iaW5lZCRtZWFuX2ltcG9ydGFuY2UsIDAuOSkNCmxlYXN0X2ltcG9ydGFudF92YXJzIDwtIGltcG9ydGFuY2VfY29tYmluZWQkdmFyaWFibGVbaW1wb3J0YW5jZV9jb21iaW5lZCRtZWFuX2ltcG9ydGFuY2UgPD0gdGhyZXNob2xkXQ0KDQojIEVsaW1pbmFyIGVzdGFzIHZhcmlhYmxlcyBkZWwgZGF0YXNldCBvcmlnaW5hbA0KZGF0YV9yZWR1Y2VkIDwtIGRhdGFbLCAhKG5hbWVzKGRhdGEpICVpbiUgbGVhc3RfaW1wb3J0YW50X3ZhcnMpXQ0KDQojIEd1YXJkYXIgZWwgZGF0YXNldCByZWR1Y2lkbw0Kd3JpdGUuY3N2KGRhdGFfcmVkdWNlZCwgImRhdGFfcmVkdWNlZC5jc3YiLCByb3cubmFtZXMgPSBGQUxTRSkNCg0KYGBgDQoNCkEgY29udGludWFjacOzbiwgc2UgcHJlc2VudGFuIGxhcyB2YXJpYWJsZXMgbWVub3MgaW1wb3J0YW50ZXMgZW4gbG9zIG1vZGVsb3MgZXZhbHVhZG9zIHkgZWwgZGF0YXNldCByZWR1Y2lkby4NCg0KYGBge3J9DQpwcmludCgiVmFyaWFibGVzIG1lbm9zIGltcG9ydGFudGVzOiIpDQpwcmludChsZWFzdF9pbXBvcnRhbnRfdmFycykNCnByaW50KCIiKQ0KcHJpbnQoIkRhdGFzZXQgcmVkdWNpZG86IikNCnByaW50KGRpbShkYXRhX3JlZHVjZWQpKQ0KDQpgYGANCg0KQ29tbyBzZSBwdWVkZSBvYnNlcnZhciwgbGFzIHZhcmlhYmxlcyBtZW5vcyBpbXBvcnRhbnRlcyBlbiBsb3MgbW9kZWxvcyBldmFsdWFkb3Mgc29uICJzeW1tZXRyeV9zZSIsICJmcmFjdGFsX2RpbWVuc2lvbl9zZSIsICJ0ZXh0dXJlX3NlIiwgInNtb290aG5lc3Nfc2UiIHkgImZyYWN0YWxfZGltZW5zaW9uX21lYW4iLiBFc3RhcyB2YXJpYWJsZXMgdGllbmVuIHVuYSBiYWphIGNvbnRyaWJ1Y2nDs24gYSBsYSBwcmVkaWNjacOzbiBkZWwgZGlhZ27Ds3N0aWNvIGVuIGNvbXBhcmFjacOzbiBjb24gb3RyYXMgY2FyYWN0ZXLDrXN0aWNhcyBtw6FzIHJlbGV2YW50ZXMsIGNvbW8gZWwgdGFtYcOxbyB5IGxhIGZvcm1hIGRlbCB0dW1vci4gRWwgZGF0YXNldCByZWR1Y2lkbyBjb250aWVuZSAzIHZhcmlhYmxlcyAoInBlcmltZXRlcl93b3JzdCIsImFyZWFfd29yc3QiLCJjb25jYXZlLnBvaW50c193b3JzdCIpDQoNCiMjIyMgMy4zLjMuIEVudHJlbmFtaWVudG8gZGUgTW9kZWxvcyBjb24gRGF0YXNldCBSZWR1Y2lkbw0KDQpFbCBzaWd1aWVudGUgcGFzbyBzZXLDoSByZXBldGlyIGVsIGFuw6FsaXNpcyBzdXBlcnZpc2FkbyBlc3RhIHZleiB1c2FuZG8gZWwgZGF0YXNldCByZWR1Y2lkbyBlbiBlbCBxdWUgbm8gYXBhcmVjZW4gbGFzIGNvbHVtbmFzICJtZW5vcyBpbXBvcnRhbnRlcyIuIERlIGVzdGEgZm9ybWEgc2UgYW5hbGl6YSBzaSBsb3MgcmVzdWx0YWRvcyBtZWpvcmFyLCBlbXBlb3JhbiBvIG5vIGFmZWN0YW4gY29uIGVsIGVzdHVkaW8gZGUgaW1wb3J0YW5jaWEgZGUgdmFyaWFubGVzOg0KDQoqKsOBcmJvbCBkZSBEZWNpc2nDs24gKENBUlQpKioNCg0KYGBge3J9DQojIMOBcmJvbCBkZSBEZWNpc2nDs24gdXRpbGl6YW5kbyBLLWZvbGQgY3Jvc3MtdmFsaWRhdGlvbg0KbW9kZWxfY2FydF9yZWR1Y2VkIDwtIHRyYWluKA0KICBkaWFnbm9zaXMgfiAuLCAgICAgICAgIyBVc2Ftb3MgdG9kYXMgbGFzIHZhcmlhYmxlcyBwcmVkaWN0b3Jhcw0KICBkYXRhID0gZGF0YV9yZWR1Y2VkLA0KICBtZXRob2QgPSAicnBhcnQiLCAgICAgIyDDgXJib2wgZGUgZGVjaXNpw7NuIChDQVJUKQ0KICB0ckNvbnRyb2wgPSB0cmFpbl9jb250cm9sLCAgIyBDb250cm9sIGRlIHZhbGlkYWNpw7NuIGNydXphZGENCiAgbWV0cmljID0gIlJPQyIgICAgICAgICMgRXZhbHVhciB1dGlsaXphbmRvIEFVQyAow4FyZWEgYmFqbyBsYSBjdXJ2YSBST0MpDQopDQoNCiMgVmVyIGVsIHJlc3VtZW4gZGVsIG1vZGVsbyBlbnRyZW5hZG8NCnByaW50KG1vZGVsX2NhcnRfcmVkdWNlZCkNCmBgYA0KDQoqKlJhbmRvbSBGb3Jlc3QqKg0KDQpgYGB7cn0NCm1vZGVsX3JmX3JlZHVjZWQgPC0gdHJhaW4oDQogIGRpYWdub3NpcyB+IC4sICAgICAgICAjIFVzYW1vcyB0b2RhcyBsYXMgdmFyaWFibGVzIHByZWRpY3RvcmFzDQogIGRhdGEgPSBkYXRhX3JlZHVjZWQsDQogIG1ldGhvZCA9ICJyZiIsICAgICAgICAjIFJhbmRvbSBGb3Jlc3QNCiAgdHJDb250cm9sID0gdHJhaW5fY29udHJvbCwgICMgQ29udHJvbCBkZSB2YWxpZGFjacOzbiBjcnV6YWRhDQogIG1ldHJpYyA9ICJST0MiICAgICAgICAjIEV2YWx1YXIgdXRpbGl6YW5kbyBBVUMgKMOBcmVhIGJham8gbGEgY3VydmEgUk9DKQ0KKQ0KDQojIFZlciBlbCByZXN1bWVuIGRlbCBtb2RlbG8gZW50cmVuYWRvDQpwcmludChtb2RlbF9yZl9yZWR1Y2VkKQ0KDQpgYGANCg0KKipTdXBwb3J0IFZlY3RvciBNYWNoaW5lIChTVk0pKioNCg0KYGBge3J9DQoNCm1vZGVsX3N2bV9yZWR1Y2VkIDwtIHRyYWluKA0KICBkaWFnbm9zaXMgfiAuLCAgICAgICAgIyBVc2Ftb3MgdG9kYXMgbGFzIHZhcmlhYmxlcyBwcmVkaWN0b3Jhcw0KICBkYXRhID0gZGF0YV9yZWR1Y2VkLA0KICBtZXRob2QgPSAic3ZtUmFkaWFsIiwgICMgU3VwcG9ydCBWZWN0b3IgTWFjaGluZSBjb24ga2VybmVsIHJhZGlhbA0KICB0ckNvbnRyb2wgPSB0cmFpbl9jb250cm9sLCAgIyBDb250cm9sIGRlIHZhbGlkYWNpw7NuIGNydXphZGENCiAgbWV0cmljID0gIlJPQyIgICAgICAgICMgRXZhbHVhciB1dGlsaXphbmRvIEFVQyAow4FyZWEgYmFqbyBsYSBjdXJ2YSBST0MpDQopDQoNCiMgVmVyIGVsIHJlc3VtZW4gZGVsIG1vZGVsbyBlbnRyZW5hZG8NCnByaW50KG1vZGVsX3N2bV9yZWR1Y2VkKQ0KDQpgYGANCg0KKipSZWdyZXNpw7NuIExvZ8Otc3RpY2EqKg0KDQpgYGB7cn0NCm1vZGVsX2xvZ2l0X3JlZHVjZWQgPC0gdHJhaW4oDQogIGRpYWdub3NpcyB+IC4sICAgICAgICAjIFVzYW1vcyB0b2RhcyBsYXMgdmFyaWFibGVzIHByZWRpY3RvcmFzDQogIGRhdGEgPSBkYXRhX3JlZHVjZWQsDQogIG1ldGhvZCA9ICJnbG0iLCAgICAgICAjIFJlZ3Jlc2nDs24gTG9nw61zdGljYQ0KICB0ckNvbnRyb2wgPSB0cmFpbl9jb250cm9sLCAgIyBDb250cm9sIGRlIHZhbGlkYWNpw7NuIGNydXphZGENCiAgbWV0cmljID0gIlJPQyIgICAgICAgICMgRXZhbHVhciB1dGlsaXphbmRvIEFVQyAow4FyZWEgYmFqbyBsYSBjdXJ2YSBST0MpDQopDQoNCiMgVmVyIGVsIHJlc3VtZW4gZGVsIG1vZGVsbyBlbnRyZW5hZG8NCnByaW50KG1vZGVsX2xvZ2l0X3JlZHVjZWQpDQpgYGANCg0KKipSZWQgTmV1cm9uYWwqKg0KDQpgYGB7cn0NCiMgRW50cmVuYXIgdW5hIHJlZCBuZXVyb25hbCBwYXJhIGNsYXNpZmljYWNpw7NuIGJpbmFyaWENCm1vZGVsX25uX3JlZHVjZWQgPC0gdHJhaW4oDQogIGRpYWdub3NpcyB+IC4sICAgICAgICAjIFVzYW1vcyB0b2RhcyBsYXMgdmFyaWFibGVzIHByZWRpY3RvcmFzDQogIGRhdGEgPSBkYXRhX3JlZHVjZWQsDQogIG1ldGhvZCA9ICJubmV0IiwgICAgICAjIFJlZCBOZXVyb25hbA0KICB0ckNvbnRyb2wgPSB0cmFpbl9jb250cm9sLCAgIyBDb250cm9sIGRlIHZhbGlkYWNpw7NuIGNydXphZGENCiAgbWV0cmljID0gIlJPQyIsICAgICAgICMgRXZhbHVhciB1dGlsaXphbmRvIEFVQyAow4FyZWEgYmFqbyBsYSBjdXJ2YSBST0MpDQogIHZlcmJvc2UgPSBGQUxTRSAgICAgICAjIFN1cHJpbWlyIGxvcyBkZXRhbGxlcyBkZWwgZW50cmVuYW1pZW50bw0KKQ0KDQpgYGANCg0KIyMjIyAzLjMuNC4gQ29tcGFyYWNpw7NuIGRlIHJlc3VsdGFkb3MgZGVsIGFuw6FsaXNpcyBzdXBlcnZpc2FkbzogZGF0YXNldCBpbmNpYWwgdnMgZGF0YXNldCByZWR1Y2lkby4NCg0KQSBjb250aW51YWNpw7NuLCBzZSBjb21wYXJhcsOhbiBsb3MgcmVzdWx0YWRvcyBkZSBsb3MgbW9kZWxvcyBlbnRyZW5hZG9zIGNvbiBlbCBkYXRhc2V0IGNvbXBsZXRvIHkgZWwgZGF0YXNldCByZWR1Y2lkby4gU2UgZXZhbHVhcsOhbiBsYXMgbcOpdHJpY2FzIGRlIHJlbmRpbWllbnRvLCBjb21vIGVsIMOhcmVhIGJham8gbGEgY3VydmEgUk9DIChBVUMtUk9DKSwgbGEgc2Vuc2liaWxpZGFkIHkgbGEgZXNwZWNpZmljaWRhZCwgcGFyYSBkZXRlcm1pbmFyIHNpIGxhIHJlZHVjY2nDs24gZGUgdmFyaWFibGVzIGFmZWN0YSBlbCByZW5kaW1pZW50byBkZSBsb3MgbW9kZWxvcy4NCg0KYGBge3J9DQojIENvbXBhcmF0aXZhIGVudHJlIGxvcyBtb2RlbG9zDQojIExpc3RhIGRlIGxvcyBtb2RlbG9zIGNvbXBsZXRvcyB5IHJlZHVjaWRvcw0KbW9kZWxvc19jb21wbGV0b3MgPC0gbGlzdChjYXJ0ID0gbW9kZWxfY2FydCwgcmYgPSBtb2RlbF9yZiwgc3ZtID0gbW9kZWxfc3ZtLCBsb2dpdCA9IG1vZGVsX2xvZ2l0LCBubiA9IG1vZGVsX25uKQ0KbW9kZWxvc19yZWR1Y2lkb3MgPC0gbGlzdChjYXJ0ID0gbW9kZWxfY2FydF9yZWR1Y2VkLCByZiA9IG1vZGVsX3JmX3JlZHVjZWQsIHN2bSA9IG1vZGVsX3N2bV9yZWR1Y2VkLCBsb2dpdCA9IG1vZGVsX2xvZ2l0X3JlZHVjZWQsIG5uID0gbW9kZWxfbm5fcmVkdWNlZCkNCg0KIyBJbmljaWFsaXphbW9zIHVuIGRhdGEgZnJhbWUgdmFjw61vIHBhcmEgYWxtYWNlbmFyIGxvcyByZXN1bHRhZG9zDQpyZXN1bHRhZG9zIDwtIGRhdGEuZnJhbWUoDQogIG1vZGVsbyA9IGNoYXJhY3RlcigpLA0KICByb2NfaW5pY2lhbCA9IG51bWVyaWMoKSwNCiAgc2Vuc19pbmljaWFsID0gbnVtZXJpYygpLA0KICBzcGVjX2luaWNpYWwgPSBudW1lcmljKCksDQogIHJvY19yZWR1Y2lkbyA9IG51bWVyaWMoKSwNCiAgc2Vuc19yZWR1Y2lkbyA9IG51bWVyaWMoKSwNCiAgc3BlY19yZWR1Y2lkbyA9IG51bWVyaWMoKSwNCiAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFDQopDQoNCiMgSXRlcmFtb3Mgc29icmUgbG9zIG1vZGVsb3MgY29tcGxldG9zIHkgcmVkdWNpZG9zDQpmb3IgKG5vbWJyZSBpbiBuYW1lcyhtb2RlbG9zX2NvbXBsZXRvcykpIHsNCiAgIyBNb2RlbG8gY29tcGxldG8NCiAgbW9kZWxvX2NvbXBsZXRvIDwtIG1vZGVsb3NfY29tcGxldG9zW1tub21icmVdXQ0KICBtZWpvcl9jb21wbGV0byA8LSBtb2RlbG9fY29tcGxldG8kcmVzdWx0c1t3aGljaC5tYXgobW9kZWxvX2NvbXBsZXRvJHJlc3VsdHMkUk9DKSwgYygiUk9DIiwgIlNlbnMiLCAiU3BlYyIpXQ0KICANCiAgIyBNb2RlbG8gcmVkdWNpZG8NCiAgbW9kZWxvX3JlZHVjaWRvIDwtIG1vZGVsb3NfcmVkdWNpZG9zW1tub21icmVdXQ0KICBtZWpvcl9yZWR1Y2lkbyA8LSBtb2RlbG9fcmVkdWNpZG8kcmVzdWx0c1t3aGljaC5tYXgobW9kZWxvX3JlZHVjaWRvJHJlc3VsdHMkUk9DKSwgYygiUk9DIiwgIlNlbnMiLCAiU3BlYyIpXQ0KICANCiAgIyBBZ3JlZ2FyIGFsIGRhdGEgZnJhbWUNCiAgcmVzdWx0YWRvcyA8LSByYmluZChyZXN1bHRhZG9zLCBkYXRhLmZyYW1lKA0KICAgIG1vZGVsbyA9IG5vbWJyZSwNCiAgICByb2NfaW5pY2lhbCA9IG1lam9yX2NvbXBsZXRvJFJPQywNCiAgICBzZW5zX2luaWNpYWwgPSBtZWpvcl9jb21wbGV0byRTZW5zLA0KICAgIHNwZWNfaW5pY2lhbCA9IG1lam9yX2NvbXBsZXRvJFNwZWMsDQogICAgcm9jX3JlZHVjaWRvID0gbWVqb3JfcmVkdWNpZG8kUk9DLA0KICAgIHNlbnNfcmVkdWNpZG8gPSBtZWpvcl9yZWR1Y2lkbyRTZW5zLA0KICAgIHNwZWNfcmVkdWNpZG8gPSBtZWpvcl9yZWR1Y2lkbyRTcGVjLA0KICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQ0KICApKQ0KfQ0KDQojIEltcHJpbWlyIGxvcyByZXN1bHRhZG9zDQpwcmludChyZXN1bHRhZG9zKQ0KDQojIFVuYSB0YWJsYSBtb3N0cmFuZG8gZWwgcmVsYXRpdmUgY2hhbmdlIGRlIGNhZGEgbcOpdHJpY2ENCmNvbXBhcmF0aXZhIDwtIHJlc3VsdGFkb3MgJT4lDQogIG11dGF0ZSgNCiAgICByb2NfY2hhbmdlID0gKHJvY19yZWR1Y2lkbyAtIHJvY19pbmljaWFsKSAvIHJvY19pbmljaWFsICwNCiAgICBzZW5zX2NoYW5nZSA9IChzZW5zX3JlZHVjaWRvIC0gc2Vuc19pbmljaWFsKSAvIHNlbnNfaW5pY2lhbCwNCiAgICBzcGVjX2NoYW5nZSA9IChzcGVjX3JlZHVjaWRvIC0gc3BlY19pbmljaWFsKSAvIHNwZWNfaW5pY2lhbA0KICApICU+JQ0KICBzZWxlY3QobW9kZWxvLCByb2NfY2hhbmdlLCBzZW5zX2NoYW5nZSwgc3BlY19jaGFuZ2UpDQoNCmZsZXh0YWJsZShjb21wYXJhdGl2YSkNCg0KYGBgDQoNCkNvbW8gc2UgcHVlZGUgdmVyIGVuIGxhIHRhYmxhLCBxdWUgbXVlc3RyYSBlbCBjYW1iaW8gcmVsYXRpdm8gZW4gbGFzIG3DqXRyaWNhcyBkZSByZW5kaW1pZW50byBkZSBsb3MgbW9kZWxvcyBjb21wbGV0b3MgeSByZWR1Y2lkb3MsIGxhIHJlZHVjY2nDs24gZGUgdmFyaWFibGVzIG5vIGFmZWN0YSBzaWduaWZpY2F0aXZhbWVudGUgZWwgcmVuZGltaWVudG8gZGUgbG9zIG1vZGVsb3MuIEVuIGdlbmVyYWwsIGxvcyBjYW1iaW9zIGVuIGVsIMOhcmVhIGJham8gbGEgY3VydmEgUk9DIChBVUMtUk9DKSwgbGEgc2Vuc2liaWxpZGFkIHkgbGEgZXNwZWNpZmljaWRhZCBzb24gbcOtbmltb3MsIGxvIHF1ZSBzdWdpZXJlIHF1ZSBsYXMgdmFyaWFibGVzIGVsaW1pbmFkYXMgbm8gdGVuw61hbiB1biBpbXBhY3RvIHNpZ25pZmljYXRpdm8gZW4gbGEgY2FwYWNpZGFkIHByZWRpY3RpdmEgZGUgbG9zIG1vZGVsb3MuIEVzdG8gaW5kaWNhIHF1ZSBlbCBkYXRhc2V0IHJlZHVjaWRvIHNpZ3VlIHNpZW5kbyBjYXBheiBkZSBjYXB0dXJhciBsb3MgcGF0cm9uZXMgcmVsZXZhbnRlcyBlbiBsb3MgZGF0b3MgeSBkZSByZWFsaXphciBwcmVkaWNjaW9uZXMgcHJlY2lzYXMgc29icmUgZWwgZGlhZ27Ds3N0aWNvIGRlIGPDoW5jZXIgZGUgbWFtYS4NCg0KQ29tbyBsb3MgbW9kZWxvcyBubyBtZWpvcmFuIG5pIGVtcGVvcmFuIHNpZ25pZmljYXRpdmFtZW50ZSBjb24gbGEgcmVkdWNjacOzbiBkZSB2YXJpYWJsZXMsIHNlIHB1ZWRlIGNvbmNsdWlyIHF1ZSBlbCBkYXRhc2V0IG9yaWdpbmFsIGNvbnRpZW5lIGluZm9ybWFjacOzbiByZWR1bmRhbnRlIG8gcG9jbyByZWxldmFudGUgcGFyYSBsYSBwcmVkaWNjacOzbiBkZWwgZGlhZ27Ds3N0aWNvLiBBZGVtw6FzLCBhaG9yYSBlcyB1biBwcm9jZXNvIG3DoXMgZWZpY2llbnRlIHkgbWVub3MgY29zdG9zbyBjb21wdXRhY2lvbmFsbWVudGUsIHlhIHF1ZSBzZSB0cmFiYWphIGNvbiB1biBuw7ptZXJvIG1lbm9yIGRlIHZhcmlhYmxlcy4NCg0KTGFzIDMgdmFyaWFibGVzIG3DoXMgaW1wb3J0YW50ZXMgcGFyYSBwcmVkZWNpciBlbCBjw6FuY2VyIGRlIG1hbWEgc29uOiAicGVyaW1ldGVyX3dvcnN0IiwiYXJlYV93b3JzdCIsImNvbmNhdmUucG9pbnRzX3dvcnN0Ii4gWWEgcXVlIHNlIGNvbnNpZ3VlIHVuIHJlc3VsdGFkbyBlcXVpdmFsZW50ZSBjb24gdW4gbWVub3IgbsO6bWVybyBkZSB2YXJpYWJsZXMsIGxvcyBtb2RlbG9zIHNvbiBtw6FzIGVmaWNpZW50ZXMgeSBtZW5vcyBwcm9wZW5zb3MgYWwgc29icmVhanVzdGUuDQoNCiMjIyAzLjQuIEFuw6FsaXNpcyBObyBTdXBlcnZpc2Fkby4NCg0KRWwgYW7DoWxpc2lzIG5vIHN1cGVydmlzYWRvIHRpZW5lIGNvbW8gb2JqZXRpdm8gaWRlbnRpZmljYXIgcGF0cm9uZXMgb2N1bHRvcyBvIGVzdHJ1Y3R1cmFzIHByZXNlbnRlcyBlbiBsb3MgZGF0b3Mgc2luIHJlcXVlcmlyIGV0aXF1ZXRhcy4gUGFyYSBlc3RlIHByb3DDs3NpdG8sIHNlIGVtcGxlYW4gdMOpY25pY2FzIGRlIGNsdXN0ZXJpbmcgeSByZWR1Y2Npw7NuIGRlIGRpbWVuc2lvbmFsaWRhZCBxdWUgcGVybWl0ZW4gZXhwbG9yYXIgbGEgaW5mb3JtYWNpw7NuIGNvbnRlbmlkYSBlbiBsYXMgY2FyYWN0ZXLDrXN0aWNhcyBkZSBsYXMgY8OpbHVsYXMuIEVzdGFzIHTDqWNuaWNhcyBmYWNpbGl0YW4gbGEgaWRlbnRpZmljYWNpw7NuIGRlIGdydXBvcyBkZSBvYnNlcnZhY2lvbmVzIHNpbWlsYXJlcyB5IHByb3BvcmNpb25hbiB1bmEgcGVyc3BlY3RpdmEgbcOhcyBjbGFyYSBzb2JyZSBsYXMgcmVsYWNpb25lcyBlbnRyZSBsYXMgdmFyaWFibGVzLg0KDQojIyMjIDMuNC4xLiBQcmVwYXJhY2nDs24gZGVsIERhdGFzZXQNCg0KUHJldmlvIGEgbGEgYXBsaWNhY2nDs24gZGUgdMOpY25pY2FzIGRlIGFuw6FsaXNpcyBubyBzdXBlcnZpc2FkbywgZXMgZnVuZGFtZW50YWwgcmVhbGl6YXIgdW4gcHJlcHJvY2VzYW1pZW50byBhZGVjdWFkbyBkZSBsb3MgZGF0b3MuIEVsIHByaW1lciBwYXNvIGNvbnNpc3RlIGVuIGVsaW1pbmFyIGxhIGNvbHVtbmEgImRpYWdub3NpcyIsIHlhIHF1ZSBkaWNoYSB2YXJpYWJsZSBjb250aWVuZSBsYSBldGlxdWV0YSBxdWUgc2UgZGVzZWEgcHJlZGVjaXIgeSBubyBzZSB1dGlsaXphIGVuIGVzdGUgdGlwbyBkZSBhbsOhbGlzaXMuIFBvc3Rlcmlvcm1lbnRlLCBzZSBub3JtYWxpemFuIGxhcyBjYXJhY3RlcsOtc3RpY2FzLCBkYWRvIHF1ZSBtdWNoYXMgdMOpY25pY2FzLCBjb21vIGVsIGNsdXN0ZXJpbmcsIHNvbiBzZW5zaWJsZXMgYSBsYXMgZXNjYWxhcyBkZSBsYXMgdmFyaWFibGVzLiBFc3RlIHByb2Nlc28gYXNlZ3VyYSBxdWUgdG9kYXMgbGFzIHZhcmlhYmxlcyB0ZW5nYW4gbGEgbWlzbWEgaW5mbHVlbmNpYSBlbiBlbCBtb2RlbG8uDQoNCmBgYHtyfQ0KZGF0YV9VblN1cGVydmlzZWQgPC0gZGF0YSAlPiUgc2VsZWN0KC1kaWFnbm9zaXMpDQpoZWFkKGRhdGFfVW5TdXBlcnZpc2VkKQ0KZGF0YV9VblN1cGVydmlzZWQkZGlhZ25vc2lzDQpgYGANCg0KTGEgbm9ybWFsaXphY2nDs24gYXNlZ3VyYSBxdWUgbGFzIHZhcmlhYmxlcyBjb24gZGlmZXJlbnRlcyB1bmlkYWRlcyBkZSBtZWRpZGEgbm8gZG9taW5lbiBlbCBhbsOhbGlzaXMgeSBldml0YSBxdWUgZWwgYWxnb3JpdG1vIGRlIGNsdXN0ZXJpbmcgbyByZWR1Y2Npw7NuIGRlIGRpbWVuc2lvbmFsaWRhZCBzZSB2ZWEgc2VzZ2FkbyBwb3IgbGEgbWFnbml0dWQgZGUgbGFzIHZhcmlhYmxlcy4gU2UgdXRpbGl6YSBsYSBmdW5jacOzbiBwcmVQcm9jZXNzIHBhcmEgY2FsY3VsYXIgbG9zIHBhcsOhbWV0cm9zIGRlIG5vcm1hbGl6YWNpw7NuIHkgY29uIGxhIGZ1bmNpw7NuIHByZWRpY3QsIHNlIGFwbGljYW4gbG9zIHBhcsOhbWV0cm9zIGNhbGN1bGFkb3MgcHJldmlhbWVudGUgYWwgY29uanVudG8gZGUgZGF0b3MsIGdlbmVyYW5kbyB1bmEgbnVldmEgdmVyc2nDs24gbm9ybWFsaXphZGEgZGUgbGFzIGNhcmFjdGVyw61zdGljYXMNCg0KYGBge3J9DQojIE5vcm1hbGl6YXIgbG9zIGRhdG9zIA0KcHJlcHJvY2Vzc19wYXJhbXMgPC0gcHJlUHJvY2VzcyhkYXRhX1VuU3VwZXJ2aXNlZFssIC1uY29sKGRhdGFfVW5TdXBlcnZpc2VkKV0sIG1ldGhvZCA9IGMoImNlbnRlciIsICJzY2FsZSIpKQ0KZGF0YV9ub3JtYWxpemVkIDwtIHByZWRpY3QocHJlcHJvY2Vzc19wYXJhbXMsIGRhdGFfVW5TdXBlcnZpc2VkWywgLW5jb2woZGF0YV9VblN1cGVydmlzZWQpXSkNCg0KYGBgDQoNCkVsIHByaW1lciBtw6l0b2RvIGRlIGFuw6FsaXNpcyBubyBzdXBlcnZpc2FkbyBxdWUgc2UgZW1wbGVhIGVzIGVsIGNsdXN0ZXJpbmcsIGVsIGN1YWwgdGllbmUgY29tbyBwcm9ww7NzaXRvIGFncnVwYXIgbGFzIG9ic2VydmFjaW9uZXMgZW4gZnVuY2nDs24gZGUgc3VzIHNpbWlsaXR1ZGVzLg0KDQojIyMjIDMuNC4yLiBDbHVzdGVyaW5nOiBEZXRlcm1pbmFjacOzbiBkZWwgTsO6bWVybyDDk3B0aW1vIGRlIENsdXN0ZXJzDQoNCkVuIGVsIGFuw6FsaXNpcyBkZSBjbHVzdGVyaW5nLCBlcyBmdW5kYW1lbnRhbCBkZXRlcm1pbmFyIGVsIG7Dum1lcm8gw7NwdGltbyBkZSBncnVwb3MgbyBjbHVzdGVycywgeWEgcXVlIGVzdGUgcGFyw6FtZXRybyBpbXBhY3RhIGRpcmVjdGFtZW50ZSBlbiBsYSBjYWxpZGFkIGRlIGxhIHNlZ21lbnRhY2nDs24uIEV4aXN0ZW4gZGl2ZXJzYXMgbWV0b2RvbG9nw61hcyBwYXJhIGlkZW50aWZpY2FyIGVzdGUgdmFsb3IsIGxhcyBjdWFsZXMgc2UgYmFzYW4gZW4gY3JpdGVyaW9zIGVzdGFkw61zdGljb3MgeSBnZW9tw6l0cmljb3MgcXVlIGV2YWzDumFuIGxhIGVzdHJ1Y3R1cmEgZGUgbG9zIGRhdG9zIHkgbGEgY29oZXNpw7NuIGRlIGxvcyBjbHVzdGVycyBmb3JtYWRvcy4NCg0KRWwgcHJpbWVyIHBhc28gcGFyYSBhcGxpY2FyIHTDqWNuaWNhcyBkZSBjbHVzdGVyaW5nIGNvbnNpc3RlIGVuIGlkZW50aWZpY2FyIGVsIG7Dum1lcm8gYWRlY3VhZG8gZGUgY2x1c3RlcnMuIERvcyBtw6l0b2RvcyBjb211bmVzIHBhcmEgZXN0ZSBwcm9ww7NzaXRvIHNvbjoNCg0KTcOpdG9kbyBkZWwgY29kbzogRXN0ZSBlbmZvcXVlIGV2YWzDumEgbGEgdmFyaWFjacOzbiBleHBsaWNhZGEgZW4gZnVuY2nDs24gZGVsIG7Dum1lcm8gZGUgY2x1c3RlcnMuIFNlIGlkZW50aWZpY2EgZWwgImNvZG8iIGVuIGxhIGdyw6FmaWNhLCBxdWUgY29ycmVzcG9uZGUgYWwgcHVudG8gZG9uZGUgbGEgbWVqb3JhIGVuIGxhIHZhcmlhbnphIGV4cGxpY2FkYSBzZSBlc3RhYmlsaXphLCBpbmRpY2FuZG8gcXVlIGHDsWFkaXIgbcOhcyBjbHVzdGVycyBubyBwcm9kdWNlIGJlbmVmaWNpb3Mgc2lnbmlmaWNhdGl2b3MuDQoNCsONbmRpY2UgZGUgU2lsaG91ZXR0ZTogRXN0ZSDDrW5kaWNlIG1pZGUgbGEgc2ltaWxpdHVkIGRlIGNhZGEgcHVudG8gY29uIHN1IHByb3BpbyBjbHVzdGVyIGVuIGNvbXBhcmFjacOzbiBjb24gb3Ryb3MgY2x1c3RlcnMuIFZhbG9yZXMgY2VyY2Fub3MgYSArMSBzdWdpZXJlbiBxdWUgbG9zIHB1bnRvcyBlc3TDoW4gY29ycmVjdGFtZW50ZSBhZ3J1cGFkb3MsIG1pZW50cmFzIHF1ZSB2YWxvcmVzIGNlcmNhbm9zIGEgLTEgaW5kaWNhbiBwb3NpYmxlcyBlcnJvcmVzIGVuIGxhIGFzaWduYWNpw7NuIGRlIGNsdXN0ZXJzLg0KDQpgYGB7cn0NCiMgTcOpdG9kbyBkZWwgY29kbw0Kd3NzIDwtIChucm93KGRhdGFfbm9ybWFsaXplZCkgLSAxKSAqIHN1bShhcHBseShkYXRhX25vcm1hbGl6ZWQsIDIsIHZhcikpDQpmb3IgKGkgaW4gMjoxNSkgd3NzW2ldIDwtIHN1bShrbWVhbnMoZGF0YV9ub3JtYWxpemVkLCBjZW50ZXJzID0gaSkkd2l0aGluc3MpDQoNCnBsb3QoMToxNSwgd3NzLCB0eXBlID0gImIiLCB4bGFiID0gIk7Dum1lcm8gZGUgQ2x1c3RlcnMiLCB5bGFiID0gIlN1bWEgZGUgQ3VhZHJhZG9zIEludGVybm9zIikNCg0KIyDDjW5kaWNlIHNpbGhvdWV0dGUNCg0Kc2lsaG91ZXR0ZV9zY29yZXMgPC0gbnVtZXJpYygpDQpmb3IgKGkgaW4gMjoxNSkgew0KICBrbSA8LSBrbWVhbnMoZGF0YV9ub3JtYWxpemVkLCBjZW50ZXJzID0gaSkNCiAgc2lsaG91ZXR0ZV9zY29yZXNbaV0gPC0gbWVhbihzaWxob3VldHRlKGttJGNsdXN0ZXIsIGRpc3QoZGF0YV9ub3JtYWxpemVkKSlbLCAzXSkNCn0NCnBsb3QoMjoxNSwgc2lsaG91ZXR0ZV9zY29yZXNbLTFdLCB0eXBlID0gImIiLCB4bGFiID0gIk7Dum1lcm8gZGUgQ2x1c3RlcnMiLCB5bGFiID0gIlB1bnRhamUgZGUgU2lsaG91ZXR0ZSIpDQoNCmBgYA0KDQpQdWVkZSB2ZXJzZSBkZSBmb3JtYSBtw6FzIGNsYXJhIGVuIGVsIHB1bnRhamUgZGUgU2lsaG91ZXR0ZSBxdWUgayBzZXLDoSAyLg0KDQpFbCBzaWd1aWVudGUgbcOpdG9kbyBkZSBhbsOhbGlzaXMgbm8gc3VwZXJ2aXNhZG8gcXVlIHNlIHV0aWxpemEgZXMgZWwgYWxnb3JpdG1vIEstbWVhbnMsIGVsIGN1YWwgcGVybWl0ZSBhZ3J1cGFyIGxvcyBkYXRvcyBlbiB1biBuw7ptZXJvIGVzcGVjw61maWNvIGRlIGNsdXN0ZXJzIHByZXZpYW1lbnRlIGRldGVybWluYWRvLg0KDQojIyMjIDMuNC4zLiBLLU1lYW5zDQoNCkVsIGFsZ29yaXRtbyBLLW1lYW5zIGVzIHVuYSB0w6ljbmljYSBkZSBjbHVzdGVyaW5nIHF1ZSBvcmdhbml6YSBsb3MgZGF0b3MgZW4gSyBjbHVzdGVycywgY29uIGVsIG9iamV0aXZvIGRlIG1pbmltaXphciBsYSB2YXJpYW56YSBkZW50cm8gZGUgY2FkYSBncnVwby4NCg0KYGBge3J9DQpzZXQuc2VlZCgxMjMpDQoNCm9wdGltYWxfY2x1c3RlcnMgPSAyDQoNCmttZWFuc19tb2RlbCA8LSBrbWVhbnMoZGF0YV9ub3JtYWxpemVkLCBjZW50ZXJzID0gb3B0aW1hbF9jbHVzdGVycywgbnN0YXJ0ID0gMjUpDQoNCiMgQWdyZWdhciBldGlxdWV0YXMgZGUgY2x1c3RlciBhbCBkYXRhc2V0IG9yaWdpbmFsDQpkYXRhJGNsdXN0ZXIgPC0ga21lYW5zX21vZGVsJGNsdXN0ZXINCg0KIyBNb3N0cmFyIGxvcyBjbHVzdGVycyBkZSBkaWZlcmVudGVzIGZvcm1hczoNCg0KIyBUYWJsYSBkZSBmcmVjdWVuY2lhcw0KdGFibGUoZGF0YSRjbHVzdGVyKQ0KDQojIFJlc3VtZW4gZGUgbG9zIGRhdG9zIGFncnVwYWRvcyBwb3IgY2x1c3Rlcg0KYWdncmVnYXRlKGRhdGFbLCAtbmNvbChkYXRhKV0sIGJ5ID0gbGlzdChjbHVzdGVyID0gZGF0YSRjbHVzdGVyKSwgRlVOID0gbWVhbikNCg0KdGFibGUoZGF0YSRkaWFnbm9zaXMsIGRhdGEkY2x1c3RlcikNCg0KDQpgYGANCiAgICAgMSAgIDINCiAgQiAzNDggICA5DQogIE0gIDM3IDE3NQ0KUG9kZW1vcyBvYnNlcnZhciBxdWUgc2UgaGFuIGNyZWFkbyBkb3MgY2x1c3RlcnMsIGNvbiAzODUgeSAxODQgIG9ic2VydmFjaW9uZXMgcmVzcGVjdGl2YW1lbnRlLiBBZGVtw6FzLCBzZSBwdWVkZSB2ZXIgY2xhcmFtZW50ZSBxdWUgZWwgY2x1c3RlciAxIGNvbnRpZW5lIHByaW5jaXBhbG1lbnRlIG9ic2VydmFjaW9uZXMgZGUgbGEgY2xhc2UgIkIiIChiZW5pZ25vKSwgbWllbnRyYXMgcXVlIGVsIGNsdXN0ZXIgMiBjb250aWVuZSBwcmluY2lwYWxtZW50ZSBvYnNlcnZhY2lvbmVzIGRlIGxhIGNsYXNlICJNIiAobWFsaWdubykuIEVzdG8gc3VnaWVyZSBxdWUgZWwgYWxnb3JpdG1vIEstbWVhbnMgaGEgc2lkbyBjYXBheiBkZSBhZ3J1cGFyIGxhcyBvYnNlcnZhY2lvbmVzIGVuIGZ1bmNpw7NuIGRlIHN1IHNpbWlsaXR1ZCwgbG8gcXVlIGZhY2lsaXRhIGxhIGlkZW50aWZpY2FjacOzbiBkZSBwYXRyb25lcyBlbiBsb3MgZGF0b3MuDQoNCiMjIyMgMy40LjQuIENsdXN0ZXJpbmcgamVyw6FycXVpY28NCg0KRWwgY2x1c3RlcmluZyBqZXLDoXJxdWljbyBlcyB1bmEgdMOpY25pY2EgcXVlIG9yZ2FuaXphIGxvcyBkYXRvcyBlbiB1bmEgZXN0cnVjdHVyYSBkZSDDoXJib2wsIGRvbmRlIGxvcyBjbHVzdGVycyBzZSBmb3JtYW4gZGUgbWFuZXJhIHJlY3Vyc2l2YSBtZWRpYW50ZSBsYSB1bmnDs24gbyBkaXZpc2nDs24gZGUgZ3J1cG9zLiBFc3RlIGVuZm9xdWUgcGVybWl0ZSB2aXN1YWxpemFyIGxhIGVzdHJ1Y3R1cmEgZGUgbG9zIGRhdG9zIGVuIGRpZmVyZW50ZXMgbml2ZWxlcyBkZSBncmFudWxhcmlkYWQgeSBmYWNpbGl0YSBsYSBpbnRlcnByZXRhY2nDs24gZGUgbGFzIHJlbGFjaW9uZXMgZW50cmUgbGFzIG9ic2VydmFjaW9uZXMuDQoNCmBgYHtyfQ0KIyBDYWxjdWxhciBkaXN0YW5jaWFzIHkgcmVhbGl6YXIgY2x1c3RlcmluZyBqZXLDoXJxdWljbw0KaGllcmFyY2hpY2FsX21vZGVsIDwtIGhjbHVzdChkaXN0KGRhdGFfbm9ybWFsaXplZCksIG1ldGhvZCA9ICJ3YXJkLkQyIikNCg0KIyBWaXN1YWxpemFyIGVsIGRlbmRyb2dyYW1hDQpwbG90KGhpZXJhcmNoaWNhbF9tb2RlbCwgbWFpbiA9ICJEZW5kcm9ncmFtYSIsIHN1YiA9ICIiLCB4bGFiID0gIiIsIGNleCA9IDAuNikNCg0KIyBDb3J0YXIgZWwgZGVuZHJvZ3JhbWEgZW4gZWwgbsO6bWVybyDDs3B0aW1vIGRlIGNsdXN0ZXJzDQpvcHRpbWFsX2NsdXN0ZXJzIDwtIDINCmRhdGEkY2x1c3RlciA8LSBjdXRyZWUoaGllcmFyY2hpY2FsX21vZGVsLCBrID0gb3B0aW1hbF9jbHVzdGVycykNCg0KIyBNb3N0cmFyIGxvcyBjbHVzdGVycyBkZSBkaWZlcmVudGVzIGZvcm1hczoNCg0KIyBUYWJsYSBkZSBmcmVjdWVuY2lhcw0KdGFibGUoZGF0YSRjbHVzdGVyKQ0KDQojIFJlc3VtZW4gZGUgbG9zIGRhdG9zIGFncnVwYWRvcyBwb3IgY2x1c3Rlcg0KYWdncmVnYXRlKGRhdGFbLCAtbmNvbChkYXRhKV0sIGJ5ID0gbGlzdChjbHVzdGVyID0gZGF0YSRjbHVzdGVyKSwgRlVOID0gbWVhbikNCg0KIA0KYGBgDQoNCkVsIGRlbmRyb2dyYW1hIG11ZXN0cmEgbGEgZXN0cnVjdHVyYSBqZXLDoXJxdWljYSBkZSBsb3MgZGF0b3MsIGRvbmRlIGxhcyBvYnNlcnZhY2lvbmVzIHNlIGFncnVwYW4gZW4gY2x1c3RlcnMgZW4gZnVuY2nDs24gZGUgc3Ugc2ltaWxpdHVkLiBBbCBjb3J0YXIgZWwgZGVuZHJvZ3JhbWEgZW4gZWwgbsO6bWVybyDDs3B0aW1vIGRlIGNsdXN0ZXJzLCBzZSBvYnRpZW5lIHVuYSBwYXJ0aWNpw7NuIGRlIGxvcyBkYXRvcyBlbiBkb3MgZ3J1cG9zLCBsbyBxdWUgcGVybWl0ZSBpZGVudGlmaWNhciBsYXMgcmVsYWNpb25lcyBlbnRyZSBsYXMgb2JzZXJ2YWNpb25lcyB5IHZpc3VhbGl6YXIgbGEgZXN0cnVjdHVyYSBzdWJ5YWNlbnRlIGRlIGxvcyBkYXRvcy4NCg0KIyMjIyAzLjQuNS4gREJTQ0FODQoNCkVsIGFsZ29yaXRtbyBEQlNDQU4gKERlbnNpdHktQmFzZWQgU3BhdGlhbCBDbHVzdGVyaW5nIG9mIEFwcGxpY2F0aW9ucyB3aXRoIE5vaXNlKSBlcyB1bmEgdMOpY25pY2EgZGUgY2x1c3RlcmluZyBxdWUgYWdydXBhIGxvcyBkYXRvcyBlbiBmdW5jacOzbiBkZSBsYSBkZW5zaWRhZCBkZSBsb3MgcHVudG9zLiBFc3RlIGVuZm9xdWUgZXMgw7p0aWwgcGFyYSBpZGVudGlmaWNhciBjbHVzdGVycyBkZSBmb3JtYXMgYXJiaXRyYXJpYXMgeSBkZXRlY3RhciBvdXRsaWVycyBlbiBsb3MgZGF0b3MuDQoNCkVuIGVzdGUgY2Fzbywgc2UgYWp1c3RhIGVsIG1vZGVsbyBEQlNDQU4gY29uIHVuIHJhZGlvIG3DoXhpbW8gZGUgMTAgeSB1biBtw61uaW1vIGRlIDIgcHVudG9zIHBhcmEgZm9ybWFyIHVuIGNsdXN0ZXIuIExvcyBwdW50b3MgcXVlIG5vIHBlcnRlbmVjZW4gYSBuaW5nw7puIGNsdXN0ZXIgc2UgY29uc2lkZXJhbiBydWlkby4NCg0KYGBge3J9DQoNCg0KIyBBanVzdGFyIGVsIG1vZGVsbyBEQlNDQU4NCmVwcyA8LSAxMCAgIyBSYWRpbyBtw6F4aW1vIHBhcmEgdmVjaW5vcw0KbWluUHRzIDwtIDIgICMgUHVudG9zIG3DrW5pbW9zIHBhcmEgZm9ybWFyIHVuIGNsdXN0ZXINCmRic2Nhbl9tb2RlbCA8LSBkYnNjYW4oZGF0YV9ub3JtYWxpemVkLCBlcHMgPSBlcHMsIG1pblB0cyA9IG1pblB0cykNCg0KIyBBZ3JlZ2FyIGV0aXF1ZXRhcyBkZSBjbHVzdGVyIGFsIGRhdGFzZXQgb3JpZ2luYWwNCmRhdGEkY2x1c3RlciA8LSBkYnNjYW5fbW9kZWwkY2x1c3Rlcg0KDQojIE1vc3RyYXIgbG9zIGNsdXN0ZXJzIGRlIGRpZmVyZW50ZXMgZm9ybWFzOg0KDQojIFRhYmxhIGRlIGZyZWN1ZW5jaWFzIChsb3MgdmFsb3JlcyAwIHNvbiBydWlkbykNCnRhYmxlKGRhdGEkY2x1c3RlcikNCg0KIyBSZXN1bWVuIGRlIGxvcyBkYXRvcyBhZ3J1cGFkb3MgcG9yIGNsdXN0ZXIgKGV4Y2x1eWVuZG8gcnVpZG8pDQphZ2dyZWdhdGUoZGF0YVtkYXRhJGNsdXN0ZXIgPiAwLCAtbmNvbChkYXRhKV0sIA0KICAgICAgICAgIGJ5ID0gbGlzdChjbHVzdGVyID0gZGF0YSRjbHVzdGVyW2RhdGEkY2x1c3RlciA+IDBdKSwgDQogICAgICAgICAgRlVOID0gbWVhbikNCg0KDQpgYGANCkNvbW8gc2UgcHVlZGUgb2JzZXJ2YXIsIGVsIGFsZ29yaXRtbyBEQlNDQU4gaWRlbnRpZmljYSAyIGNsdXN0ZXJzIHkgYXNpZ25hIGxvcyBwdW50b3MgcXVlIG5vIHBlcnRlbmVjZW4gYSBuaW5nw7puIGNsdXN0ZXIgY29tbyBydWlkby4gVW5vIGRlIGVsbG9zIGNvbiA1NjYgcG9yIGxvIHF1ZSBubyBoYSByZWFsaXphZG8gdW5hIGJ1ZW5hIGFncnVwYWNpw7NuLg0KDQpgYGB7cn0NCg0KIyBDYWxjdWxhciBlbCBjb2VmaWNpZW50ZSBkZSBzaWx1ZXRhIHBhcmEgZGlmZXJlbnRlcyBtw6l0b2Rvcw0Kc2lsaG91ZXR0ZV9rbWVhbnMgPC0gc2lsaG91ZXR0ZShrbWVhbnNfbW9kZWwkY2x1c3RlciwgZGlzdChkYXRhX25vcm1hbGl6ZWQpKQ0Kc2lsaG91ZXR0ZV9oaWVyYXJjaGljYWwgPC0gc2lsaG91ZXR0ZShjdXRyZWUoaGllcmFyY2hpY2FsX21vZGVsLCBrID0gb3B0aW1hbF9jbHVzdGVycyksIGRpc3QoZGF0YV9ub3JtYWxpemVkKSkNCnNpbGhvdWV0dGVfZGJzY2FuIDwtIHNpbGhvdWV0dGUoZGJzY2FuX21vZGVsJGNsdXN0ZXIsIGRpc3QoZGF0YV9ub3JtYWxpemVkKSkNCg0KIyBWaXN1YWxpemFyIGxvcyBjb2VmaWNpZW50ZXMNCnBsb3Qoc2lsaG91ZXR0ZV9rbWVhbnMsIG1haW4gPSAiU2lsdWV0YSAtIEstTWVhbnMiKQ0KcGxvdChzaWxob3VldHRlX2hpZXJhcmNoaWNhbCwgbWFpbiA9ICJTaWx1ZXRhIC0gQ2x1c3RlcmluZyBKZXLDoXJxdWljbyIpDQpwbG90KHNpbGhvdWV0dGVfZGJzY2FuLCBtYWluID0gIlNpbHVldGEgLSBEQlNDQU4iKQ0KYGBgDQpDb21vIHJlc3VsdGFkbyBkZSBsYSBjb21wYXJhY2nDs24sIGVsIGstbWVhbnMgdGllbmUgdW5hIG1lZGlhIGRlIGFuY2h1cmEgZGUgc2lsdWV0YSBkZSAwLDM1LCBlbCBjbHVzdGVyaW5nIGplcsOhcnF1aWNvIGRlIDAsMjkgeSBlbCBEQlNDQU4gZGUgMCwzNS4gRXN0byBzdWdpZXJlIHF1ZSBsb3MgY2x1c3RlcnMgZ2VuZXJhZG9zIHBvciBrLW1lYW5zIHkgREJTQ0FOIHNvbiBtw6FzIGNvaGVzaXZvcyB5IGJpZW4gZGVmaW5pZG9zIGVuIGNvbXBhcmFjacOzbiBjb24gZWwgY2x1c3RlcmluZyBqZXLDoXJxdWljby4gRWwgREJTQ0FOIHkgay1tZWFucyB0aWVuZW4gdW5hIG1heW9yIGNvaGVzacOzbiB5IHNlcGFyYWNpw7NuIGVudHJlIGxvcyBjbHVzdGVycywgbG8gcXVlIGluZGljYSBxdWUgZXN0b3MgYWxnb3JpdG1vcyBzb24gbcOhcyBlZmVjdGl2b3MgcGFyYSBhZ3J1cGFyIGxvcyBkYXRvcyBlbiBmdW5jacOzbiBkZSBsYSBzaW1pbGl0dWQgZW50cmUgbGFzIG9ic2VydmFjaW9uZXMuDQoNCg0KRWwgc2lndWllbnRlIHBhc28gZW4gbnVlc3RybyBhbsOhbGlzaXMgbm8gc3VwZXJ2aXNhZG8gZXMgYXBsaWNhciBlbCBBbsOhbGlzaXMgZGUgQ29tcG9uZW50ZXMgUHJpbmNpcGFsZXMgKFBDQSkuIEVzdGEgdMOpY25pY2EgcGVybWl0aXLDoSB2aXN1YWxpemFyIGxhIGVzdHJ1Y3R1cmEgZGUgbG9zIGRhdG9zLCBhc8OtIGNvbW8gdmFsaWRhciBsb3MgcmVzdWx0YWRvcyBvYnRlbmlkb3MgYSB0cmF2w6lzIGRlbCBjbHVzdGVyaW5nLg0KDQojIyMjIDMuNC40LiBSZWR1Y2Npw7NuIGRlIGRpbWVuc2lvbmFsaWRhZCBjb24gUENBDQoNCkxhIGRpbWVuc2lvbmFsaWRhZCBzZSByZWR1Y2lyw6EgdHJhbnNmb3JtYW5kbyBsYXMgdmFyaWFibGVzIG9yaWdpbmFsZXMgZW4gY29tcG9uZW50ZXMgcHJpbmNpcGFsZXMgcGFyYSBpZGVudGlmaWNhciBsYSBtYXlvciBwYXJ0ZSBkZSBsYSB2YXJpYWJpbGlkYWQgZW4gbG9zIGRhdG9zLiBFc3RvIGZhY2lsaXRhIGxhIHZpc3VhbGl6YWNpw7NuIGRlIGxhIGVzdHJ1Y3R1cmEgc3VieWFjZW50ZSB5IG9wdGltaXphIGxhIGludGVycHJldGFjacOzbiBkZSBsb3MgcmVzdWx0YWRvcyBkZWwgY2x1c3RlcmluZy4gUENBIHNpbXBsaWZpY2EgZWwgYW7DoWxpc2lzIGFsIHJlZHVjaXIgbGEgY29tcGxlamlkYWQgZGUgbG9zIGRhdG9zLiBTZWxlY2Npb25hbW9zIGxvcyBjb21wb25lbnRlcyBwcmluY2lwYWxlcyBxdWUgZXhwbGlxdWVuIGFsIG1lbm9zIGVsIDk1JSBkZSBsYSB2YXJpYW56YSBhY3VtdWxhZGEuDQoNCmBgYHtyfQ0KcGNhX21vZGVsIDwtIHByY29tcChkYXRhX25vcm1hbGl6ZWQsIHNjYWxlID0gVFJVRSkNCmV4cGxhaW5lZF92YXJpYW5jZSA8LSBjdW1zdW0ocGNhX21vZGVsJHNkZXZeMiAvIHN1bShwY2FfbW9kZWwkc2Rldl4yKSkNCm51bV9jb21wb25lbnRzIDwtIHdoaWNoKGV4cGxhaW5lZF92YXJpYW5jZSA+PSAwLjk1KVsxXSAgIyBTZWxlY2Npb25hciBjb21wb25lbnRlcyBxdWUgZXhwbGlxdWVuIGVsIDk1JSBkZSBsYSB2YXJpYW56YQ0KZGF0YV9wY2EgPC0gYXMuZGF0YS5mcmFtZShwY2FfbW9kZWwkeFssIDE6bnVtX2NvbXBvbmVudHNdKQ0KDQoNCg0KYGBgDQoNCg0KIyMjIyAzLjQuNS4gUmVkdWNjacOzbiBkZSBkaW1lbnNpb25hbGlkYWQgY29uIHQtU05FDQoNCnQtZGlzdHJpYnV0ZWQgU3RvY2hhc3RpYyBOZWlnaGJvciBFbWJlZGRpbmcgZXMgdW5hIHTDqWNuaWNhIG5vIHN1cGVydmlzYWRhIGRlIHJlZHVjY2nDs24gbm8gbGluZWFsIGRlIGxhIGRpbWVuc2lvbmFsaWRhZCBwYXJhIGxhIGV4cGxvcmFjacOzbiBkZSBkYXRvcyB5IGxhIHZpc3VhbGl6YWNpw7NuIGRlIGRhdG9zIGRlIGFsdGEgZGltZW5zacOzbjoNCg0KDQpgYGB7cn0NCg0Kc2V0LnNlZWQoNDIpDQp0c25lX21vZGVsIDwtIFJ0c25lKGRhdGFfbm9ybWFsaXplZCwgcGVycGxleGl0eSA9IDMwLCB0aGV0YSA9IDAuNSwgZGltcyA9IDIpDQpkYXRhX3RzbmUgPC0gYXMuZGF0YS5mcmFtZSh0c25lX21vZGVsJFkpDQpjb2xuYW1lcyhkYXRhX3RzbmUpIDwtIGMoIkRpbTEiLCAiRGltMiIpDQoNCmBgYA0KDQoNCiMjIyMgMy40LjYuIENsdXN0ZXJpbmcgZW4gbG9zIERhdGFzZXRzIFJlZHVjaWRvcw0KDQpBcGxpY2Ftb3MgSy1tZWFucyBhIGxvcyBkYXRhc2V0cyBnZW5lcmFkb3MgcG9yIFBDQSB5IHQtU05FLCB1dGlsaXphbmRvIGVsIG1pc21vIG7Dum1lcm8gZGUgY2x1c3RlcnMgZGV0ZXJtaW5hZG8gcHJldmlhbWVudGUuDQoNCiMjIyMgMy40LjYuMSBLLW1lYW5zIGNvbiBQQ0ENCg0KYGBge3J9DQprbWVhbnNfcGNhIDwtIGttZWFucyhkYXRhX3BjYSwgY2VudGVycyA9IG9wdGltYWxfY2x1c3RlcnMsIG5zdGFydCA9IDI1KQ0KZGF0YV9wY2EkY2x1c3RlciA8LSBrbWVhbnNfcGNhJGNsdXN0ZXINCg0KIyBWaXN1YWxpemFjacOzbg0KZ2dwbG90KGRhdGFfcGNhLCBhZXMoeCA9IFBDMSwgeSA9IFBDMiwgY29sb3IgPSBhcy5mYWN0b3IoY2x1c3RlcikpKSArDQogIGdlb21fcG9pbnQoKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIGxhYnModGl0bGUgPSAiQ2x1c3RlcmluZyBLLW1lYW5zIChQQ0EpIikNCg0KYGBgDQoNCg0KIyMjIyAzLjQuNi4yIEstbWVhbnMgY29uIHQtU05FDQoNCg0KYGBge3J9DQprbWVhbnNfdHNuZSA8LSBrbWVhbnMoZGF0YV90c25lLCBjZW50ZXJzID0gb3B0aW1hbF9jbHVzdGVycywgbnN0YXJ0ID0gMjUpDQpkYXRhX3RzbmUkY2x1c3RlciA8LSBrbWVhbnNfdHNuZSRjbHVzdGVyDQoNCiMgVmlzdWFsaXphY2nDs24NCmdncGxvdChkYXRhX3RzbmUsIGFlcyh4ID0gRGltMSwgeSA9IERpbTIsIGNvbG9yID0gYXMuZmFjdG9yKGNsdXN0ZXIpKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICBsYWJzKHRpdGxlID0gIkNsdXN0ZXJpbmcgSy1tZWFucyAodC1TTkUpIikNCg0KYGBgDQoNCg0KIyMjIyAzLjQuNi4zIENvbXBhcmFjacOzbiBkZSBSZXN1bHRhZG9zDQoNCmBgYHtyfQ0KIyBWaXN1YWxpemFjacOzbiBQQ0ENCmdncGxvdChkYXRhX3BjYSwgYWVzKHggPSBQQzEsIHkgPSBQQzIsIGNvbG9yID0gYXMuZmFjdG9yKGNsdXN0ZXIpKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICBsYWJzKHRpdGxlID0gIkNsdXN0ZXJzIHZpc3VhbGl6YWRvcyBlbiBlc3BhY2lvIFBDQSIpDQoNCiMgVmlzdWFsaXphY2nDs24gdC1TTkUNCmdncGxvdChkYXRhX3RzbmUsIGFlcyh4ID0gRGltMSwgeSA9IERpbTIsIGNvbG9yID0gYXMuZmFjdG9yKGNsdXN0ZXIpKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICBsYWJzKHRpdGxlID0gIkNsdXN0ZXJzIHZpc3VhbGl6YWRvcyBlbiBlc3BhY2lvIHQtU05FIikNCg0KYGBgDQoNCg0KU2UgdmlzdWFsaXphbiBjbGFyYW1lbnRlIGRvcyBjbMO6c3RlcmVzLg0KDQpMb3MgY2x1c3RlcnMgZXN0w6FuIGJpZW4gZGVmaW5pZG9zIHkgc2VwYXJhZG9zIGVzcGFjaWFsbWVudGUuDQoNCkV4aXN0ZSB1biBncmFkbyBtZW5vciBkZSBzb2xhcGFtaWVudG8gZW50cmUgbG9zIGNsdXN0ZXJzLCBsbyBxdWUgc3VnaWVyZSBxdWUgbGFzIHZhcmlhYmxlcyBvcmlnaW5hbGVzIG9mcmVjZW4gdW5hIHNlcGFyYWNpw7NuIG3DoXMgY2xhcmEgZW50cmUgbGFzIGNsYXNlcyBlbiBlc3RlIGVzcGFjaW8uDQoNCg0KIyMjIyAzLjQuNi40IMONbmRpY2UgU2lsb3VoZXR0ZQ0KDQpQYXJhIHZpc3VhbGl6YXIgbWVqb3IgbGEgY29tcGFyYWNpw7NuIGRlIGVsbG8gdmFtb3MgYSB1c2FyIGVsIFNpbGhvdWV0dGUgU2NvcmUsIHVuYSBtw6l0cmljYSB1dGlsemFkYSBwYXJhIGV2YWx1YXIgbGEgY2FsaWRhZCBkZSBsb3MgY2x1c3RlcnMuIEVsIHZhbG9yIG9zY2lsYSBlbnRyZSAtMSB5IDEsIGRvbmRlOg0KDQotIFVuIHZhbG9yIGNlcmNhbm8gYSAxIGluZGljYSBxdWUgbG9zIHB1bnRvcyBlc3TDoW4gYmllbiBhZ3J1cGFkb3MuDQotIFVuIHZhbG9yIGNlcmNhbm8gYSAwIGluZGljYSBxdWUgbG9zIHB1bnRvcyBlc3TDoW4gZW4gbGEgZnJvbnRlcmEsIGVzIGRlY2lyLCBlbCBhZ3J1cGFtaWVudG8gcmVzdWx0YSBhbWJpZ3VvLg0KLSBVbiB2YWxvciBjZXJjYW5vIGEgLTEgc3VnaWVyZSBxdWUgbG9zIHB1bnRvcyBwb2Ryw61hbiBoYWJlciBzaWRvIGFzaWduYWRvcyBhbCBjbHVzdGVyIGluY29ycmVjdG8sIGVzIGRlY2lyLCBlbCBtb2RlbG8gbm8gbG9ncsOzIGFncnVwYXJsb3MgYWRlY3VhZGFtZW50ZS4gDQoNCmBgYHtyfQ0KIyBTaWxob3VldHRlIHBhcmEgY2x1c3RlcmluZyBjb24gUENBDQpzaWxob3VldHRlX3BjYSA8LSBtZWFuKHNpbGhvdWV0dGUoa21lYW5zX3BjYSRjbHVzdGVyLCBkaXN0KGRhdGFfcGNhKSlbLCAzXSkNCg0KIyBTaWxob3VldHRlIHBhcmEgY2x1c3RlcmluZyBjb24gdC1TTkUNCnNpbGhvdWV0dGVfdHNuZSA8LSBtZWFuKHNpbGhvdWV0dGUoa21lYW5zX3RzbmUkY2x1c3RlciwgZGlzdChkYXRhX3RzbmUpKVssIDNdKQ0KDQojIENvbXBhcmFjacOzbg0KY2F0KCJTaWxob3VldHRlIFNjb3JlIC0gU2luIHJlZHVjY2nDs246Iiwgc2lsaG91ZXR0ZV9zY29yZXNbb3B0aW1hbF9jbHVzdGVyc10sICJcbiIpDQpjYXQoIlNpbGhvdWV0dGUgU2NvcmUgLSBQQ0E6Iiwgc2lsaG91ZXR0ZV9wY2EsICJcbiIpDQpjYXQoIlNpbGhvdWV0dGUgU2NvcmUgLSB0LVNORToiLCBzaWxob3VldHRlX3RzbmUsICJcbiIpDQoNCmBgYA0KU2lsaG91ZXR0ZSBTY29yZSAtIFNpbiByZWR1Y2Npw7NuOiAwLjM0ODIwMzEgDQpTaWxob3VldHRlIFNjb3JlIC0gUENBOiAwLjM2NzgwOTYgDQpTaWxob3VldHRlIFNjb3JlIC0gdC1TTkU6IDAuNTIxNjIwNiANCkVsIFNpbGhvdWV0dGUgU2NvcmUgc2luIHJlZHVjY2nDs24gZXMgMC4zNCwgbG8gcXVlIGluZGljYSB1bmEgbWVkaWEgY2FsaWRhZCBkZSBsb3MgY2x1c3RlcnMuIFVuIHZhbG9yIGNlcmNhbm8gYSAwLjcgZXMgZ2VuZXJhbG1lbnRlIGNvbnNpZGVyYWRvIGNvbW8gdW4gaW5kaWNhZG9yIGRlIHF1ZSBlbCBtb2RlbG8gZXN0w6EgbG9ncmFuZG8gdW5hIGJ1ZW5hIHNlcGFyYWNpw7NuIGVudHJlIGxvcyBjbHVzdGVycyB5IHF1ZSBsb3MgcHVudG9zIGRlbnRybyBkZSBjYWRhIGNsdXN0ZXIgc29uIGJhc3RhbnRlIHNpbWlsYXJlcyBlbnRyZSBzw60uDQpFbCBTaWxob3VldHRlIFNjb3JlIGRlc3B1w6lzIGRlIHJlYWxpemFyIFBDQSBlcyAwLjM2LCBsbyBxdWUgaW5kaWNhIHVuYSBtZWpvcmEgZW4gbGEgY2FsaWRhZCBkZSBsb3MgY2x1c3RlcnMgZW4gY29tcGFyYWNpw7NuIGNvbiBsb3MgZGF0b3Mgb3JpZ2luYWxlcy4gRXN0byBzdWdpZXJlIHF1ZSBsYSByZWR1Y2Npw7NuIGRlIGRpbWVuc2lvbmFsaWRhZCBoYSBwZXJtaXRpZG8gdW5hIG1lam9yIHNlcGFyYWNpw7NuIGRlIGxvcyBjbHVzdGVycyB5IHVuYSBtYXlvciBjb2hlc2nDs24gZGVudHJvIGRlIGNhZGEgY2x1c3Rlci4NCkVsIFNpbGhvdWV0dGUgU2NvcmUgZGVzcHXDqXMgZGUgcmVhbGl6YXIgdC1TTkUgZXMgMC41MiwgbG8gcXVlIGluZGljYSB1bmEgbWVqb3JhIHNpZ25pZmljYXRpdmEgZW4gbGEgY2FsaWRhZCBkZSBsb3MgY2x1c3RlcnMgZW4gY29tcGFyYWNpw7NuIGNvbiBsb3MgZGF0b3Mgb3JpZ2luYWxlcyB5IGNvbiBsYSByZWR1Y2Npw7NuIGRlIGRpbWVuc2lvbmFsaWRhZCBtZWRpYW50ZSBQQ0EuIEVzdG8gc3VnaWVyZSBxdWUgdC1TTkUgaGEgbG9ncmFkbyB1bmEgbWVqb3Igc2VwYXJhY2nDs24gZGUgbG9zIGNsdXN0ZXJzIHkgdW5hIG1heW9yIGNvaGVzacOzbiBkZW50cm8gZGUgY2FkYSBjbHVzdGVyLg0KDQoNCg0KRWwgc2lndWllbnRlIGFsZ29yaXRtbyB1dGlsaXphZG8gZW4gZWwgYW7DoWxpc2lzIG5vIHN1cGVydmlzYWRvIGVzIGVsIEFwcmlvcmkgcGFyYSByZWdsYXMgZGUgYXNvY2lhY2nDs24uDQoNCiMjIyMgMy40LjYuIFJlZ2xhcyBkZSBhc29jaWFjacOzbiBjb24gYWxnb3JpdG1vIEFwcmlvcmkuDQoNCkxhcyByZWdsYXMgZGUgYXNvY2lhY2nDs24gc2UgZW1wbGVhbiBlbiBEYXRhIE1pbmluZyBwYXJhIGlkZW50aWZpY2FyIHJlbGFjaW9uZXMgZW50cmUgZWxlbWVudG9zIGRlIHVuIGNvbmp1bnRvIGRlIGRhdG9zLiBFc3RhcyByZWxhY2lvbmVzIGF5dWRhbiBhIGVuY29udHJhciBwYXRyb25lcyBlbiBsb3MgZGF0b3MgeSBlc3TDoW4gZm9ybWFkYXMgcG9yIHVuIGFudGVjZWRlbnRlIHthfSB5IHVuIGNvbnNlY3VlbnRlIHtifSwgaW5kaWNhbmRvIHF1ZSB7YX0gLVw+IHtifS4gRXMgcmVsZXZhbnRlIGNvbm9jZXIgdMOpcm1pbm9zIGNvbW86DQoNClNvcG9ydGU6IFByb3BvcmNpw7NuIGRlIHRyYW5zYWNjaW9uZXMgcXVlIGNvbnRpZW5lbiBhbWJvcyBjb25qdW50b3MgZGUgw610ZW1zIChhbnRlY2VkZW50ZSB5IGNvbnNlY3VlbnRlKS4gQ29uZmlhbnphOiBJbmRpY2Fkb3IgZGUgY3XDoW4gYmllbiBwcmVkaWNlIGVsIGFudGVjZWRlbnRlIGFsIGNvbnNlY3VlbnRlLg0KDQoqQWxnb3JpdG1vIEFwcmlvcmkqDQoNClNlIHV0aWxpemFyw6EgZWwgQWxnb3JpdG1vIEFwcmlvcmkgcGFyYSBvYnRlbmVyIGxhcyByZWdsYXMgZGUgYXNvY2lhY2nDs24sIHlhIHF1ZSByZXN1bHRhIMO6dGlsIHBhcmEgaWRlbnRpZmljYXIgaXRlbXNldHMgZnJlY3VlbnRlcyBlbiBkYXRvcyB0cmFuc2FjY2lvbmFsZXMsIGNvbW8gZXZlbnRvcyByZWdpc3RyYWRvcyBlbiB1biBpbnRlcnZhbG8gZGUgdGllbXBvIGRldGVybWluYWRvLg0KDQojIyMjIyBGYXNlIDE6IFJlZHVjY2nDs24gZGVsIG7Dum1lcm8gZGUgY2FuZGlkYXRvcw0KDQoxLiAgUHJpbWVybywgc2UgZ2VuZXJhbiB0b2RvcyBsb3MgaXRlbXNldHMgY29uIHVuIMO6bmljbyDDrXRlbS4gTHVlZ28sIGVzdG9zIGl0ZW1zZXRzIHNlIGNvbWJpbmFuIHBhcmEgZm9ybWFyIGl0ZW1zZXRzIGNvbiBkb3MgZWxlbWVudG9zLCB5IGFzw60gc3VjZXNpdmFtZW50ZS4gU2Ugc2VsZWNjaW9uYXLDoW4gw7puaWNhbWVudGUgbG9zIHBhcmVzIGN1eW8gc29wb3J0ZSBzZWEgbWF5b3IgbyBpZ3VhbCBhIHVuIHVtYnJhbCBtaW5zdXAsIGVsaW1pbmFuZG8gYXF1ZWxsb3MgcXVlIG5vIGN1bXBsYW4gY29uIGVzdGUgY3JpdGVyaW8uDQoNClNlIGluc3RhbGFyw6EgZWwgcGFxdWV0ZSBuZWNlc2FyaW8gKGFydWxlcyksIHF1ZSBlcyB1biBwYXF1ZXRlIGVuIFIgcXVlIHByb3BvcmNpb25hIGZ1bmNpb25hbGlkYWRlcyBwYXJhIHRyYWJhamFyIGNvbiByZWdsYXMgZGUgYXNvY2lhY2nDs24uDQoNCg0KMi4gIFNlIGNhcmdhbiBsb3MgZGF0b3MgY29tbyB0cmFuc2FjY2lvbmVzIGVuIGVsIGZvcm1hdG8gYGJhc2tldGAsIGludGVycHJldGFuZG8gY2FkYSB0cmFuc2FjY2nDs24gY29tbyB1biBjb25qdW50byBkZSDDrXRlbXMgKGJhc2tldCkuIEVzdG8gcGVybWl0ZSBtYW5lamFyIHRhbnRvIGRhdG9zIGNhdGVnw7NyaWNvcyAoY29tbyBkaWFnbm9zaXMpIGNvbW8gY29udGludW9zIChsb3Mgb3Ryb3MgYXRyaWJ1dG9zKSBlbiBsYSBtaXNtYSB0cmFuc2FjY2nDs24uDQoNCmBgYHtyfQ0KdHJhbnNhY2Npb25lcyA8LSByZWFkLnRyYW5zYWN0aW9ucygiZGF0YV9yZWR1Y2VkLmNzdiIsIGZvcm1hdCA9ICJiYXNrZXQiLCBzZXAgPSAiLCIpDQoNCmBgYA0KDQojIyMjIyBGYXNlIDI6IEdlbmVyYXIgcmVnbGFzIFBhcsOhbWV0cm9zOiBzb3BvcnRlIG3DrW5pbW8gKDAuMDEgLVw+IDElKSB5IGNvbmZpYW56YSBtw61uaW1hICg4MCUpDQoNCkVuIGVzdGEgZmFzZSwgc2UgcHJvY2VkZXLDoSBhIGdlbmVyYXIgbGFzIHJlZ2xhcyBkZSBhc29jaWFjacOzbiB1dGlsaXphbmRvIGxvcyBwYXLDoW1ldHJvcyBkZSBzb3BvcnRlIG3DrW5pbW8gKDAuMDEgLSAxJSkgeSBjb25maWFuemEgbcOtbmltYSAoODAlKS4gRXN0b3MgdmFsb3JlcyBwZXJtaXRlbiBmaWx0cmFyIGxhcyByZWdsYXMgcXVlIHNvbiBzdWZpY2llbnRlbWVudGUgZnJlY3VlbnRlcyB5IGNvbmZpYWJsZXMsIGFzZWd1cmFuZG8gcXVlIHNlIG9idGVuZ2FuIHJlbGFjaW9uZXMgc2lnbmlmaWNhdGl2YXMgZW50cmUgbG9zIMOtdGVtcyBlbiBsb3MgZGF0b3MuDQoNCmBgYHtyfQ0KcmVnbGFzQXNvY2lhY2lvbiA8LSBhcHJpb3JpKHRyYW5zYWNjaW9uZXMsIHBhcmFtZXRlciA9IGxpc3Qoc3VwcCA9IDAuMDEsIGNvbmYgPSAwLjgpKQ0KDQpgYGANCg0KU2UgZmlsdHJhbiBsYXMgcmVnbGFzIGJhc2FkYXMgZW4gZWwgbGlmdCB5IGxhIGNvbmZpYW56YSwgZGUgbW9kbyBxdWUgc29sbyBzZSBjb25zaWRlcmVuIGFxdWVsbGFzIGNvbiB1biBsaWZ0IG1heW9yIGEgMS41IHkgdW5hIGNvbmZpYW56YSBzdXBlcmlvciBhbCA4MCUuIFVuIGxpZnQgXD4gMS41IGluZGljYSB1bmEgZnVlcnRlIGFzb2NpYWNpw7NuIGVudHJlIGxvcyDDrXRlbXMgZW4gbGEgcmVnbGEsIG1pZW50cmFzIHF1ZSB1bmEgY29uZmlhbnphIOKJpSAwLjggYXNlZ3VyYSBxdWUgbGEgcHJvYmFiaWxpZGFkIGRlIHF1ZSBlbCBjb25zZWN1ZW50ZSBvY3VycmEgY3VhbmRvIGVsIGFudGVjZWRlbnRlIGVzdMOhIHByZXNlbnRlIGVzIGFsdGEuDQoNCmBgYHtyfQ0KZmlsdHJhZGFzIDwtIHN1YnNldChyZWdsYXNBc29jaWFjaW9uLCBsaWZ0ID4gMS41ICYgY29uZmlkZW5jZSA+PSAwLjgpDQppbnNwZWN0KGZpbHRyYWRhcykNCg0KDQpgYGANCg0KQ29tbyBzZSBwdWVkZSBvYnNlcnZhciwgc2UgaGEgZ2VuZXJhZG8gdW5hIHJlZ2xhIGRlIGFzb2NpYWNpw7NuIGNvbiB1biBzb3BvcnRlIGRlbCAyLjI4JSwgdW5hIGNvbmZpYW56YSBkZWwgMTAwJSB5IHVuIGxpZnQgZGUgMS41OS4gDQoNCk5vIHNlIGhhIGNvbnNlZ3VpZG8gZW5jb250cmFyIHJlZ2xhcyBkZSBhc29jaWFjacOzbiBzaWduaWZpY2F0aXZhcyBjb24gbG9zIGRhdG9zIHJlZHVjaWRvcy4gRXN0byBwdWVkZSBkZWJlcnNlIGEgbGEgZmFsdGEgZGUgdmFyaWFiaWxpZGFkIGVuIGxvcyBkYXRvcyBvIGEgbGEgcmVkdWNjacOzbiBkZSB2YXJpYWJsZXMsIGxvIHF1ZSBsaW1pdGEgbGEgY2FwYWNpZGFkIGRlbCBhbGdvcml0bW8gcGFyYSBlbmNvbnRyYXIgcmVsYWNpb25lcyBzaWduaWZpY2F0aXZhcyBlbnRyZSBsb3Mgw610ZW1zLg0KDQojIyMjIDMuNC42LjEgVmlzdWFsaXphY2nDs24gZGUgbGFzIFJlZ2xhcyBkZSBBc29jaWFjacOzbjoNCg0KYGBge3J9DQojIEluc3RhbGFyIGVsIHBhcXVldGUgZGUgdmlzdWFsaXphY2nDs24NCg0KDQojIFZpc3VhbGl6YXIgbGFzIHJlZ2xhcw0KcGxvdChmaWx0cmFkYXMsIG1ldGhvZCA9ICJncmFwaCIpDQoNCmBgYA0KDQoNCg0KDQoNCiMjIyA0LiBDb25jbHVzaW9uZXMNCg0KDQpIZW1vcyBsbGV2YWRvIGEgY2FibyB1biBhbsOhbGlzaXMgZXhoYXVzdGl2byBkZWwgY29uanVudG8gZGUgZGF0b3MgZGUgY8OhbmNlciBkZSBtYW1hLCBleHBsb3JhbmRvIHRhbnRvIG1vZGVsb3MgZGUgYXByZW5kaXphamUgc3VwZXJ2aXNhZG8gY29tbyBubyBzdXBlcnZpc2FkbyB5IGxhIGlkZW50aWZpY2FjacOzbiBkZSByZWdsYXMgZGUgYXNvY2lhY2nDs24uIExvcyBtb2RlbG9zIFJhbmRvbSBGb3Jlc3QgeSBTVk0gZGVzdGFjYXJvbiBwb3Igc3UgcmVuZGltaWVudG8gZXhjZXBjaW9uYWwsIGxvZ3JhbmRvIGFsdGFzIHB1bnR1YWNpb25lcyBkZSBBVUMgeSBzZW5zaWJpbGlkYWQuIExhcyB2YXJpYWJsZXMgbcOhcyBpbXBvcnRhbnRlcyBwYXJhIHByZWRlY2lyIGVsIGPDoW5jZXIgZGUgbWFtYSBmdWVyb24gInBlcmltZXRlcl93b3JzdCIsICJhcmVhX3dvcnN0IiB5ICJjb25jYXZlLnBvaW50c193b3JzdCIuIExhIGVsaW1pbmFjacOzbiBkZSB2YXJpYWJsZXMgbWVub3MgaW1wb3J0YW50ZXMgbm8gYWZlY3TDsyBzaWduaWZpY2F0aXZhbWVudGUgZWwgZGVzZW1wZcOxbyBkZSBsb3MgbW9kZWxvcywgbG8gcXVlIHN1Z2llcmUgcXVlIGVzdGFzIHZhcmlhYmxlcyBubyBhcG9ydGFiYW4gaW5mb3JtYWNpw7NuIHJlbGV2YW50ZSBuaSBpbnRyb2R1Y8OtYW4gcnVpZG8sIHBlcm1pdGllbmRvIG1vZGVsb3MgbcOhcyBlZmljaWVudGVzIHkgbWVub3MgcHJvcGVuc29zIGFsIHNvYnJlYWp1c3RlLiBFbiBlbCBhcHJlbmRpemFqZSBubyBzdXBlcnZpc2FkbywgZWwgY2x1c3RlcmluZyBzaW4gcmVkdWNjacOzbiBkZSBkaW1lbnNpb25hbGlkYWQgcHJvcG9yY2lvbsOzIGxhIG1lam9yIHNlcGFyYWNpw7NuIGVudHJlIGNsdXN0ZXJzLCBjb24gdW4gU2lsaG91ZXR0ZSBTY29yZSBtw6FzIGFsdG8gZGUgMC42OC4gU2luIGVtYmFyZ28sIGxhIFBDQSByZWR1am8gc2lnbmlmaWNhdGl2YW1lbnRlIGxhIGNhbGlkYWQgZGVsIGNsdXN0ZXJpbmcgYWwgZWxpbWluYXIgY29tcG9uZW50ZXMgY2xhdmUuIHQtU05FIG1vc3Ryw7MgdW5hIGNhbGlkYWQgaW50ZXJtZWRpYSAoMC41NCkgZW4gY29tcGFyYWNpw7NuIGNvbiBlbCBlc3BhY2lvIG9yaWdpbmFsIGRlIGxhcyB2YXJpYWJsZXMsIGRlc3RhY2FuZG8gc3UgdXRpbGlkYWQgcGFyYSBsYSB2aXN1YWxpemFjacOzbiBwZXJvIGxpbWl0YW5kbyBsYSBwcmVzZXJ2YWNpw7NuIGRlIGxhIGVzdHJ1Y3R1cmEgZGVsIGNsdXN0ZXJpbmcuIEVzdGUgYW7DoWxpc2lzIHN1YnJheWEgbGEgaW1wb3J0YW5jaWEgZGUgZW1wbGVhciBkaXZlcnNhcyB0w6ljbmljYXMgZGUgYW7DoWxpc2lzIGRlIGRhdG9zIHBhcmEgb2J0ZW5lciB1bmEgY29tcHJlbnNpw7NuIGNvbXBsZXRhIGRlIGxvcyBwYXRyb25lcyB5IHJlbGFjaW9uZXMgZW4gbG9zIGRhdG9zIGRlIGPDoW5jZXIgZGUgbWFtYS4NCg0KIyMjIDUuIFJlZmVyZW5jaWFzDQoNCi0gICBbVUNJIE1hY2hpbmUgTGVhcm5pbmcgUmVwb3NpdG9yeTogQnJlYXN0IENhbmNlciBXaXNjb25zaW4gKERpYWdub3N0aWMpIERhdGEgU2V0XShodHRwczovL2FyY2hpdmUuaWNzLnVjaS5lZHUvbWwvZGF0YXNldHMvQnJlYXN0K0NhbmNlcitXaXNjb25zaW4rKERpYWdub3N0aWMpKQ0KLSAgIEFzaWduYXR1cmEgZGUgRnVuZGFtZW50b3MgZGUgaW5nZW5pZXLDrWEgZGUgZGF0b3MgZGUgbGEgVW5pdmVyc2lkYWQgZGUgU2V2aWxsYS4NCg==